From 169e06d83cef5116f0e39c74d2c497f04ba6278d Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 29 May 2024 16:55:35 +0200 Subject: [PATCH 001/128] refactor: update isValidDate return type for improved type safety --- packages/time/src/tests/isValidDate.test.ts | 38 +++++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/time/src/tests/isValidDate.test.ts b/packages/time/src/tests/isValidDate.test.ts index 57a3556..188c31c 100644 --- a/packages/time/src/tests/isValidDate.test.ts +++ b/packages/time/src/tests/isValidDate.test.ts @@ -1,16 +1,32 @@ -import {describe, expect, test} from 'vitest'; -import {isValidDate} from '../utils/isValidDate'; +import { describe, expect, test } from 'vitest' +import { isValidDate } from '../utils/isValidDate' describe('isValidDate', () => { test('should return true for a valid date', () => { - expect(isValidDate(new Date())).toBe(true); - }); + const date = new Date(); + expect(isValidDate(date)).toBe(true) + }) - test('should return false for an invalid date', () => { - expect(isValidDate(new Date("invalid"))).toBe(false); - }); + test.each([ + '2021-10-10', + new Date('invalid'), + {}, + undefined, + null, + NaN, + 0, + ])('should return false for invalid date %p', (date) => { + expect(isValidDate(date)).toBe(false) + }) - test("should return false for null", () => { - expect(isValidDate(null)).toBe(false); - }); -}); \ No newline at end of file + test('should assert type guards correctly', () => { + const notADate = 'not a date'; + if (isValidDate(notADate)) { + expect(notADate).toBeInstanceOf(Date) + notADate.getDate() + } else { + // @ts-expect-error + notADate.getTime() + } + }) +}) From 75cb7b331a5c2ee41263f45509378197e9645b60 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 29 May 2024 16:55:47 +0200 Subject: [PATCH 002/128] refactor: update isValidDate return type for improved type safety --- packages/time/src/utils/isValidDate.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/time/src/utils/isValidDate.ts b/packages/time/src/utils/isValidDate.ts index 987df31..c0d1599 100644 --- a/packages/time/src/utils/isValidDate.ts +++ b/packages/time/src/utils/isValidDate.ts @@ -4,9 +4,6 @@ * @param date Date * @returns boolean */ -export function isValidDate(date: any): boolean { - if (Object.prototype.toString.call(date) !== '[object Date]') { - return false; - } - return date.getTime() === date.getTime(); -} \ No newline at end of file +export function isValidDate(date: unknown): date is Date { + return date instanceof Date && !isNaN(date.getTime()); +} From 5dca8d5e763a5e7cb2879ae836fec9cd16070a89 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 30 May 2024 17:52:05 +0200 Subject: [PATCH 003/128] refactor: update isValidDate return type for improved type safety --- packages/time/src/tests/isValidDate.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/time/src/tests/isValidDate.test.ts b/packages/time/src/tests/isValidDate.test.ts index 188c31c..4d01970 100644 --- a/packages/time/src/tests/isValidDate.test.ts +++ b/packages/time/src/tests/isValidDate.test.ts @@ -25,8 +25,10 @@ describe('isValidDate', () => { expect(notADate).toBeInstanceOf(Date) notADate.getDate() } else { - // @ts-expect-error - notADate.getTime() + expect(() => { + // @ts-expect-error + notADate.getTime() + }).toThrowError() } }) }) From e9e4be9c36c35283dded374d5475822fd888d203 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 30 May 2024 22:29:49 +0200 Subject: [PATCH 004/128] revert: isValidDate util --- packages/time/src/tests/isValidDate.test.ts | 38 ++++++--------------- packages/time/src/utils/isValidDate.ts | 9 +++-- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/packages/time/src/tests/isValidDate.test.ts b/packages/time/src/tests/isValidDate.test.ts index 4d01970..01afdc6 100644 --- a/packages/time/src/tests/isValidDate.test.ts +++ b/packages/time/src/tests/isValidDate.test.ts @@ -1,34 +1,16 @@ -import { describe, expect, test } from 'vitest' -import { isValidDate } from '../utils/isValidDate' +import {describe, expect, test} from 'vitest'; +import {isValidDate} from '../utils/isValidDate'; describe('isValidDate', () => { test('should return true for a valid date', () => { - const date = new Date(); - expect(isValidDate(date)).toBe(true) + expect(isValidDate(new Date())).toBe(true); }) - test.each([ - '2021-10-10', - new Date('invalid'), - {}, - undefined, - null, - NaN, - 0, - ])('should return false for invalid date %p', (date) => { - expect(isValidDate(date)).toBe(false) - }) + test('should return false for an invalid date', () => { + expect(isValidDate(new Date("invalid"))).toBe(false); + }); - test('should assert type guards correctly', () => { - const notADate = 'not a date'; - if (isValidDate(notADate)) { - expect(notADate).toBeInstanceOf(Date) - notADate.getDate() - } else { - expect(() => { - // @ts-expect-error - notADate.getTime() - }).toThrowError() - } - }) -}) + test("should return false for null", () => { + expect(isValidDate(null)).toBe(false); + }); +}); diff --git a/packages/time/src/utils/isValidDate.ts b/packages/time/src/utils/isValidDate.ts index c0d1599..987df31 100644 --- a/packages/time/src/utils/isValidDate.ts +++ b/packages/time/src/utils/isValidDate.ts @@ -4,6 +4,9 @@ * @param date Date * @returns boolean */ -export function isValidDate(date: unknown): date is Date { - return date instanceof Date && !isNaN(date.getTime()); -} +export function isValidDate(date: any): boolean { + if (Object.prototype.toString.call(date) !== '[object Date]') { + return false; + } + return date.getTime() === date.getTime(); +} \ No newline at end of file From d64d04a6a7bddb4feee5ed3af4736158aa3597a3 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sat, 1 Jun 2024 21:24:36 +0200 Subject: [PATCH 005/128] feat: useCalendar hook --- packages/react-time/package.json | 1 + packages/react-time/src/useCalendar.ts | 350 +++++++++++++++++++++++++ pnpm-lock.yaml | 64 +++-- 3 files changed, 389 insertions(+), 26 deletions(-) create mode 100644 packages/react-time/src/useCalendar.ts diff --git a/packages/react-time/package.json b/packages/react-time/package.json index c3f9d13..a784c85 100644 --- a/packages/react-time/package.json +++ b/packages/react-time/package.json @@ -62,6 +62,7 @@ "react-dom": "^17.0.0 || ^18.0.0" }, "dependencies": { + "@js-temporal/polyfill": "^0.4.4", "@tanstack/time": "workspace:*", "use-sync-external-store": "^1.2.0" }, diff --git a/packages/react-time/src/useCalendar.ts b/packages/react-time/src/useCalendar.ts new file mode 100644 index 0000000..e77606d --- /dev/null +++ b/packages/react-time/src/useCalendar.ts @@ -0,0 +1,350 @@ +import { useCallback, useMemo, useState } from 'react' +import { Temporal } from '@js-temporal/polyfill' +import type { CSSProperties, MouseEventHandler } from 'react' + +export interface Event { + id: string + startDate: Temporal.PlainDateTime + endDate: Temporal.PlainDateTime + title: string +} + +const getFirstDayOfMonth = (currMonth: string) => + Temporal.PlainDate.from(`${currMonth}-01`) + +const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { + const date = Temporal.PlainDate.from(currWeek) + return date.subtract({ days: (date.dayOfWeek - weekStartsOn + 7) % 7 }) +} + +const getChunks = function* (arr: T[], n: number) { + for (let i = 0; i < arr.length; i += n) { + yield arr.slice(i, i + n) + } +} + +const splitMultiDayEvents = (event: Event) => { + const startDate = Temporal.PlainDateTime.from(event.startDate) + const endDate = Temporal.PlainDateTime.from(event.endDate) + const events: Event[] = [] + + let currentDay = startDate + while ( + Temporal.PlainDate.compare( + currentDay.toPlainDate(), + endDate.toPlainDate(), + ) < 0 + ) { + const startOfCurrentDay = currentDay.with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }) + const endOfCurrentDay = currentDay.with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }) + + const eventStart = + Temporal.PlainDateTime.compare(currentDay, startDate) === 0 + ? startDate + : startOfCurrentDay + const eventEnd = + Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 + ? endDate + : endOfCurrentDay + + events.push({ + ...event, + startDate: eventStart, + endDate: eventEnd, + }) + + currentDay = startOfCurrentDay.add({ days: 1 }) + } + + return events +} + +interface UseCalendarProps { + weekStartsOn?: number + events: Event[] + viewMode: 'month' | 'week' | number + locale?: string + onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void +} + +const generateDateRange = ( + start: Temporal.PlainDate, + end: Temporal.PlainDate, +) => { + const dates: Temporal.PlainDate[] = [] + let current = start + while (Temporal.PlainDate.compare(current, end) <= 0) { + dates.push(current) + current = current.add({ days: 1 }) + } + return dates +} + +export const useCalendar = ({ + weekStartsOn = 1, + events, + viewMode: initialViewMode, + locale, + onChangeViewMode, +}: UseCalendarProps) => { + const today = Temporal.Now.plainDateISO() + + const [currPeriod, setCurrPeriod] = useState(today) + const [viewMode, setViewMode] = useState(initialViewMode) + + const firstDayOfMonth = getFirstDayOfMonth( + currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), + ) + const firstDayOfWeek = getFirstDayOfWeek(currPeriod.toString(), weekStartsOn) + + const days = + viewMode === 'month' + ? Array.from( + getChunks( + generateDateRange( + firstDayOfMonth, + firstDayOfMonth.add({ months: 1 }).subtract({ days: 1 }), + ), + 7, + ), + ) + : viewMode === 'week' + ? Array.from( + getChunks( + generateDateRange( + firstDayOfWeek, + firstDayOfWeek.add({ days: 6 }), + ), + 7, + ), + ) + : Array.from( + getChunks( + generateDateRange( + currPeriod, + currPeriod.add({ days: viewMode - 1 }), + ), + viewMode, + ), + ) + + const eventMap = useMemo(() => { + const map = new Map() + + events.forEach((event) => { + const eventStartDate = Temporal.PlainDateTime.from(event.startDate) + const eventEndDate = Temporal.PlainDateTime.from(event.endDate) + if ( + Temporal.PlainDate.compare( + eventStartDate.toPlainDate(), + eventEndDate.toPlainDate(), + ) !== 0 + ) { + const splitEvents = splitMultiDayEvents(event) + splitEvents.forEach((splitEvent) => { + const splitKey = splitEvent.startDate.toString().split('T')[0] + if (splitKey && !map.has(splitKey)) { + map.set(splitKey, []) + map.get(splitKey)?.push(splitEvent) + } + }) + } else { + const eventKey = event.startDate.toString().split('T')[0] + if (eventKey && !map.has(eventKey)) { + map.set(eventKey, []) + map.get(eventKey)?.push(event) + } + } + }) + + return map + }, [events]) + + const daysWithEvents = days.map((dayChunk) => { + return dayChunk.map((day) => { + const dayKey = day.toString() + const dailyEvents = eventMap.get(dayKey) ?? [] + + return { + date: day, + events: dailyEvents, + } + }) + }) + + const getPrev = useCallback>(() => { + switch (viewMode) { + case 'month': { + const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: 1 }) + setCurrPeriod(firstDayOfPrevMonth) + break + } + case 'week': { + const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: 1 }) + setCurrPeriod(firstDayOfPrevWeek) + break + } + default: { + const prevCustomStart = currPeriod.subtract({ days: viewMode }) + setCurrPeriod(prevCustomStart) + break + } + } + }, [viewMode, firstDayOfMonth, firstDayOfWeek, currPeriod]) + + const getNext = useCallback>(() => { + switch (viewMode) { + case 'month': { + const firstDayOfNextMonth = firstDayOfMonth.add({ months: 1 }) + setCurrPeriod(firstDayOfNextMonth) + break + } + case 'week': { + const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: 1 }) + setCurrPeriod(firstDayOfNextWeek) + break + } + default: { + const nextCustomStart = currPeriod.add({ days: viewMode }) + setCurrPeriod(nextCustomStart) + break + } + } + }, [viewMode, firstDayOfMonth, firstDayOfWeek, currPeriod]) + + const getCurrent = useCallback>(() => { + setCurrPeriod(today) + }, [today]) + + const get = useCallback((date: Temporal.PlainDate) => { + setCurrPeriod(date) + }, []) + + const chunks = + viewMode === 'month' ? [...getChunks(daysWithEvents, 7)] : [daysWithEvents] + const changeViewMode = useCallback( + (newViewMode: 'month' | 'week' | number) => { + onChangeViewMode?.(newViewMode) + setViewMode(newViewMode) + }, + [onChangeViewMode], + ) + + const getEventProps = useCallback( + (id: Event['id']): { style: CSSProperties } | null => { + const event = [...eventMap.values()] + .flat() + .find((event) => event.id === id) + if (!event) return null + + const eventStartDate = Temporal.PlainDateTime.from(event.startDate) + const eventEndDate = Temporal.PlainDateTime.from(event.endDate) + + const isSplitEvent = + Temporal.PlainDate.compare( + eventStartDate.toPlainDate(), + eventEndDate.toPlainDate(), + ) !== 0 + + let percentageOfDay + let eventHeightInMinutes + + if (isSplitEvent) { + const isStartPart = + eventStartDate.hour !== 0 || eventStartDate.minute !== 0 + if (isStartPart) { + const eventTimeInMinutes = + eventStartDate.hour * 60 + eventStartDate.minute + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 + eventHeightInMinutes = 24 * 60 - eventTimeInMinutes + } else { + percentageOfDay = 0 + eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute + } + } else { + const eventTimeInMinutes = + eventStartDate.hour * 60 + eventStartDate.minute + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 + const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute + eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes + } + + const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20) + + const overlappingEvents = [...eventMap.values()].flat().filter((e) => { + const eStartDate = Temporal.PlainDateTime.from(e.startDate) + const eEndDate = Temporal.PlainDateTime.from(e.endDate) + return ( + (e.id !== id && + Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && + Temporal.PlainDateTime.compare(eventStartDate, eEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eventEndDate, eStartDate) >= 0 && + Temporal.PlainDateTime.compare(eventEndDate, eEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eStartDate, eventStartDate) >= 0 && + Temporal.PlainDateTime.compare(eStartDate, eventEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eEndDate, eventStartDate) >= 0 && + Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) + ) + }) + + const eventIndex = overlappingEvents.findIndex((e) => e.id === id) + const totalOverlaps = overlappingEvents.length + const sidePadding = 2 + const innerPadding = 2 + const totalInnerPadding = (totalOverlaps - 1) * innerPadding + const availableWidth = 100 - totalInnerPadding - 2 * sidePadding + const eventWidth = + totalOverlaps > 0 + ? availableWidth / totalOverlaps + : 100 - 2 * sidePadding + const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) + + if (viewMode === 'week' || typeof viewMode === 'number') { + return { + style: { + position: 'absolute', + top: `min(${percentageOfDay}%, calc(100% - 55px))`, + left: `${eventLeft}%`, + width: `${eventWidth}%`, + margin: 0, + height: `${eventHeight}%`, + }, + } + } + + return null + }, + [eventMap, viewMode], + ) + + return { + firstDayOfPeriod: + viewMode === 'month' + ? firstDayOfMonth + : viewMode === 'week' + ? firstDayOfWeek + : currPeriod, + currPeriod: currPeriod.toString({ calendarName: 'auto' }), + getPrev, + getNext, + getCurrent, + get, + chunks, + daysNames: days + .flat() + .map((day) => day.toLocaleString(locale, { weekday: 'short' })), + viewMode, + changeViewMode, + getEventProps, + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37c8fc7..e032657 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,6 +150,9 @@ importers: packages/react-time: dependencies: + '@js-temporal/polyfill': + specifier: ^0.4.4 + version: 0.4.4 '@tanstack/time': specifier: workspace:* version: link:../time @@ -541,7 +544,7 @@ packages: dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.24.2 - '@babel/generator': 7.23.6 + '@babel/generator': 7.24.1 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0) '@babel/helpers': 7.24.1 @@ -2619,6 +2622,14 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@js-temporal/polyfill@0.4.4: + resolution: {integrity: sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==} + engines: {node: '>=12'} + dependencies: + jsbi: 4.3.0 + tslib: 2.6.2 + dev: false + /@leichtgewicht/ip-codec@2.0.5: resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} dev: true @@ -5113,7 +5124,7 @@ packages: dom-serializer: 2.0.0 domhandler: 5.0.3 htmlparser2: 8.0.2 - postcss: 8.4.35 + postcss: 8.4.38 postcss-media-query-parser: 0.2.3 dev: true @@ -5151,12 +5162,12 @@ packages: webpack: optional: true dependencies: - icss-utils: 5.1.0(postcss@8.4.35) - postcss: 8.4.35 - postcss-modules-extract-imports: 3.0.0(postcss@8.4.35) - postcss-modules-local-by-default: 4.0.4(postcss@8.4.35) - postcss-modules-scope: 3.1.1(postcss@8.4.35) - postcss-modules-values: 4.0.0(postcss@8.4.35) + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-modules-extract-imports: 3.0.0(postcss@8.4.38) + postcss-modules-local-by-default: 4.0.4(postcss@8.4.38) + postcss-modules-scope: 3.1.1(postcss@8.4.38) + postcss-modules-values: 4.0.0(postcss@8.4.38) postcss-value-parser: 4.2.0 semver: 7.6.0 webpack: 5.90.3(esbuild@0.20.2) @@ -7006,13 +7017,13 @@ packages: safer-buffer: 2.1.2 dev: true - /icss-utils@5.1.0(postcss@8.4.35): + /icss-utils@5.1.0(postcss@8.4.38): resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.35 + postcss: 8.4.38 dev: true /identity-function@1.0.0: @@ -7495,7 +7506,7 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.24.0 + '@babel/core': 7.24.3 '@babel/parser': 7.24.1 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -7641,6 +7652,10 @@ packages: argparse: 2.0.1 dev: true + /jsbi@4.3.0: + resolution: {integrity: sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==} + dev: false + /jsdom@24.0.0: resolution: {integrity: sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==} engines: {node: '>=18'} @@ -7891,8 +7906,6 @@ packages: peerDependenciesMeta: webpack: optional: true - webpack-sources: - optional: true dependencies: webpack: 5.90.3(esbuild@0.20.2) webpack-sources: 3.2.3 @@ -9113,45 +9126,45 @@ packages: resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} dev: true - /postcss-modules-extract-imports@3.0.0(postcss@8.4.35): + /postcss-modules-extract-imports@3.0.0(postcss@8.4.38): resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.35 + postcss: 8.4.38 dev: true - /postcss-modules-local-by-default@4.0.4(postcss@8.4.35): + /postcss-modules-local-by-default@4.0.4(postcss@8.4.38): resolution: {integrity: sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0(postcss@8.4.35) - postcss: 8.4.35 + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 postcss-selector-parser: 6.0.16 postcss-value-parser: 4.2.0 dev: true - /postcss-modules-scope@3.1.1(postcss@8.4.35): + /postcss-modules-scope@3.1.1(postcss@8.4.38): resolution: {integrity: sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.35 + postcss: 8.4.38 postcss-selector-parser: 6.0.16 dev: true - /postcss-modules-values@4.0.0(postcss@8.4.35): + /postcss-modules-values@4.0.0(postcss@8.4.38): resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0(postcss@8.4.35) - postcss: 8.4.35 + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 dev: true /postcss-selector-parser@6.0.16: @@ -9458,7 +9471,7 @@ packages: /regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.24.1 dev: true /regex-parser@2.3.0: @@ -9545,7 +9558,7 @@ packages: adjust-sourcemap-loader: 4.0.0 convert-source-map: 1.9.0 loader-utils: 2.0.4 - postcss: 8.4.35 + postcss: 8.4.38 source-map: 0.6.1 dev: true @@ -10741,7 +10754,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: true /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} From 28b43046ca47236c01941c0d139d3e51d37afdf9 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 2 Jun 2024 23:28:45 +0200 Subject: [PATCH 006/128] test: useCalendar tests --- packages/react-time/src/index.ts | 4 +- .../react-time/src/tests/useCalendar.test.ts | 99 +++++++++++++++++++ packages/react-time/src/useCalendar.ts | 2 +- packages/react-time/vite.config.ts | 4 +- 4 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 packages/react-time/src/tests/useCalendar.test.ts diff --git a/packages/react-time/src/index.ts b/packages/react-time/src/index.ts index dabac73..0b95ae9 100644 --- a/packages/react-time/src/index.ts +++ b/packages/react-time/src/index.ts @@ -1,3 +1 @@ -/** - * TanStack Time - */ \ No newline at end of file +export { useCalendar } from './useCalendar'; diff --git a/packages/react-time/src/tests/useCalendar.test.ts b/packages/react-time/src/tests/useCalendar.test.ts new file mode 100644 index 0000000..d1548c4 --- /dev/null +++ b/packages/react-time/src/tests/useCalendar.test.ts @@ -0,0 +1,99 @@ +import { Temporal } from '@js-temporal/polyfill'; +import { describe, expect, test } from 'vitest'; +import { act, renderHook } from '@testing-library/react'; +import { useCalendar } from '../useCalendar'; + +describe('useCalendar', () => { + const events = [ + { + id: '1', + startDate: Temporal.PlainDateTime.from('2024-06-01T10:00:00'), + endDate: Temporal.PlainDateTime.from('2024-06-01T12:00:00'), + title: 'Event 1', + }, + { + id: '2', + startDate: Temporal.PlainDateTime.from('2024-06-02T14:00:00'), + endDate: Temporal.PlainDateTime.from('2024-06-02T16:00:00'), + title: 'Event 2', + }, + ]; + + const mockEvent = {} as React.MouseEvent; + + test('should initialize with the correct view mode and current period', () => { + const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + expect(result.current.viewMode).toBe('month'); + expect(result.current.currPeriod).toBe(Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }).substring(0, 7)); + }); + + test('should navigate to the previous period correctly', () => { + const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + + act(() => { + result.current.getPrev(mockEvent); + }); + + const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ months: 1 }); + expect(result.current.currPeriod).toBe(expectedPreviousMonth.toString({ calendarName: 'auto' }).substring(0, 7)); + }); + + test('should navigate to the next period correctly', () => { + const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + + act(() => { + result.current.getNext(mockEvent); + }); + + const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }); + expect(result.current.currPeriod).toBe(expectedNextMonth.toString({ calendarName: 'auto' }).substring(0, 7)); + }); + + test('should reset to the current period correctly', () => { + const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + + act(() => { + result.current.getNext(mockEvent); + result.current.getCurrent(mockEvent); + }); + + expect(result.current.currPeriod).toBe(Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }).substring(0, 7)); + }); + + test('should change view mode correctly', () => { + const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + + act(() => { + result.current.changeViewMode('week'); + }); + + expect(result.current.viewMode).toBe('week'); + }); + + test('should select a day correctly', () => { + const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + + act(() => { + result.current.get(Temporal.PlainDate.from('2024-06-01')); + }); + + expect(result.current.currPeriod).toBe('2024-06-01'); + }); + + test('should return the correct props for an event', () => { + const { result } = renderHook(() => useCalendar({ events, viewMode: 'week' })); + + const eventProps = result.current.getEventProps('1'); + + expect(eventProps).toEqual({ + style: { + position: 'absolute', + top: '41.66666666666667%', + left: '2%', + width: '96%', + margin: 0, + height: '5.555555555555555%', + }, + }); + }); +}); diff --git a/packages/react-time/src/useCalendar.ts b/packages/react-time/src/useCalendar.ts index e77606d..edfdf18 100644 --- a/packages/react-time/src/useCalendar.ts +++ b/packages/react-time/src/useCalendar.ts @@ -244,7 +244,7 @@ export const useCalendar = ({ (id: Event['id']): { style: CSSProperties } | null => { const event = [...eventMap.values()] .flat() - .find((event) => event.id === id) + .find((currEvent) => currEvent.id === id) if (!event) return null const eventStartDate = Temporal.PlainDateTime.from(event.startDate) diff --git a/packages/react-time/vite.config.ts b/packages/react-time/vite.config.ts index 68b8411..36d0656 100644 --- a/packages/react-time/vite.config.ts +++ b/packages/react-time/vite.config.ts @@ -1,11 +1,9 @@ import { defineConfig, mergeConfig } from 'vitest/config' import { tanstackBuildConfig } from '@tanstack/config/build' -import react from '@vitejs/plugin-react' const config = defineConfig({ - plugins: [react()], test: { - name: 'react-time', + name: 'time', dir: './src', watch: false, environment: 'jsdom', From 28f491c7375436be29e4e80cb325d1850cec06d0 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 2 Jun 2024 23:29:44 +0200 Subject: [PATCH 007/128] docs: useCalendar hook --- docs/framework/react/reference/uesCalendar.md | 65 +++++++++++++++++++ docs/framework/react/reference/useStore.md | 28 -------- 2 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 docs/framework/react/reference/uesCalendar.md delete mode 100644 docs/framework/react/reference/useStore.md diff --git a/docs/framework/react/reference/uesCalendar.md b/docs/framework/react/reference/uesCalendar.md new file mode 100644 index 0000000..d798fc5 --- /dev/null +++ b/docs/framework/react/reference/uesCalendar.md @@ -0,0 +1,65 @@ +--- +title: Use Calendar +id: useCalendar +--- + +### `useCalendar` + +```tsx +export function useCalendar({ + weekStartsOn, + events, + viewMode, + locale, + onChangeViewMode, +}: UseCalendarProps) +``` + +`useCalendar` is a hook that provides a comprehensive set of functionalities for managing calendar events, view modes, and period navigation. + + +#### Parameters + +- `weekStartsOn?: number` + - This parameter is an optional number that specifies the day of the week that the calendar should start on. It defaults to 0, which is Sunday. +- `events: Event[]` + - This parameter is an array of events that the calendar should display. +- `viewMode: 'month' | 'week' | number` + - This parameter is a string that specifies the initial view mode of the calendar. It can be either 'month', 'week', or a number representing the number of days in a custom view mode. +- `locale?: string` + - This parameter is an optional string that specifies the locale to use for formatting dates and times. It defaults to the system locale. +- `onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void` + - This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. +- `onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void` + - This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. + + +#### Returns + +- `firstDayOfPeriod: Temporal.PlainDate` + - This value represents the first day of the current period displayed by the calendar. +- `currPeriod: string` + - This value represents a string that describes the current period displayed by the calendar. +- `getPrev: MouseEventHandler` + - This function is a click event handler that navigates to the previous period. +- `getNext: MouseEventHandler` + - This function is a click event handler that navigates to the next period. +- `getCurrent: MouseEventHandler` + - This function is a click event handler that navigates to the current period. +- `get: (date: Temporal.PlainDate) => void` + - This function is a callback function that is called when a date is selected in the calendar. It receives the selected date as an argument. +- `chunks: Array>` + - This value represents the calendar grid, where each cell contains the date and events for that day. +- `daysNames: string[]` + - This value represents an array of strings that contain the names of the days of the week. +- `viewMode: 'month' | 'week' | number` + - This value represents the current view mode of the calendar. +- `changeViewMode: (newViewMode: 'month' | 'week' | number) => void` + - This function is used to change the view mode of the calendar. +- `getEventProps: (id: string) => { style: CSSProperties } | null` + - This function is used to retrieve the style properties for a specific event based on its ID. +- `getEventProps: (id: string) => { style: CSSProperties } | null` + - This function is used to retrieve the style properties for a specific event based on its ID. +- `getEventProps: (id: string) => { style: CSSProperties } | null` + - This function is used to retrieve the style properties for a specific event based on its ID. + diff --git a/docs/framework/react/reference/useStore.md b/docs/framework/react/reference/useStore.md deleted file mode 100644 index 0a709c5..0000000 --- a/docs/framework/react/reference/useStore.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Use Store -id: useStore ---- -### `useStore` - -```tsx -export function useStore< - TState, - TSelected = NoInfer, - TUpdater extends AnyUpdater = AnyUpdater, ->( - store: Store, - selector: (state: NoInfer) => TSelected = (d) => d as any, -) -``` - -useStore is a custom hook that returns the updated store given the intial store and the selector. React can use this to keep your component subscribed to the store and re-render it on changes. - - - -#### Parameters -- `store: Store` - - This parameter represents the external store itself which holds the entire state of your application. It expects an instance of a `@tanstack/store` that manages state and supports updates through a callback. -- `selector: (state: NoInfer) => TSelected = (d) => d as any` - - This parameter is a callback function that takes the state of the store and expects you to return a sub-state of the store. This selected sub-state is subsequently utilized as the state displayed by the useStore hook. It triggers a re-render of the component only when there are changes in this data, ensuring that updates to the displayed state trigger the necessary re-renders - - From 468dc262149029341d79295598023323e350f095 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 2 Jun 2024 23:32:43 +0200 Subject: [PATCH 008/128] docs: useCalendar hook --- docs/framework/react/reference/uesCalendar.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/docs/framework/react/reference/uesCalendar.md b/docs/framework/react/reference/uesCalendar.md index d798fc5..91a0650 100644 --- a/docs/framework/react/reference/uesCalendar.md +++ b/docs/framework/react/reference/uesCalendar.md @@ -63,3 +63,79 @@ export function useCalendar({ - `getEventProps: (id: string) => { style: CSSProperties } | null` - This function is used to retrieve the style properties for a specific event based on its ID. + +#### Example Usage + +```tsx +const CalendarComponent = ({ events }) => { + const { + firstDayOfPeriod, + currPeriod, + getPrev, + getNext, + getCurrent, + get, + changeViewMode, + chunks, + daysNames, + viewMode, + getEventProps, + } = useCalendar({ + events, + viewMode: 'month', + locale: 'en-US', + onChangeViewMode: (newViewMode) => console.log('View mode changed:', newViewMode), + }); + + return ( +
+
+ + + +
+
+ + + + +
+
+ {viewMode === 'month' && ( +
+ {daysNames.map((dayName, index) => ( +
+ {dayName} +
+ ))} +
+ )} +
+ {chunks.map((week, weekIndex) => ( +
+ {week.map((day) => ( +
+
+ {day.date.day} +
+
+ {day.events.map((event) => ( +
+ {event.title} +
+ ))} +
+
+ ))} +
+ ))} +
+
+
+ ); +}; +``` \ No newline at end of file From 0b9bdeb6ee4818791da704c8ff6c0c914d03cde9 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 2 Jun 2024 23:39:32 +0200 Subject: [PATCH 009/128] test: useCalendar tests --- .../react-time/src/tests/useCalendar.test.ts | 115 +++++++++++------- 1 file changed, 70 insertions(+), 45 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.ts b/packages/react-time/src/tests/useCalendar.test.ts index d1548c4..c808afd 100644 --- a/packages/react-time/src/tests/useCalendar.test.ts +++ b/packages/react-time/src/tests/useCalendar.test.ts @@ -1,7 +1,7 @@ -import { Temporal } from '@js-temporal/polyfill'; -import { describe, expect, test } from 'vitest'; -import { act, renderHook } from '@testing-library/react'; -import { useCalendar } from '../useCalendar'; +import { Temporal } from '@js-temporal/polyfill' +import { describe, expect, test } from 'vitest' +import { act, renderHook } from '@testing-library/react' +import { useCalendar } from '../useCalendar' describe('useCalendar', () => { const events = [ @@ -17,83 +17,108 @@ describe('useCalendar', () => { endDate: Temporal.PlainDateTime.from('2024-06-02T16:00:00'), title: 'Event 2', }, - ]; + ] - const mockEvent = {} as React.MouseEvent; + const mockEvent = {} as React.MouseEvent test('should initialize with the correct view mode and current period', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); - expect(result.current.viewMode).toBe('month'); - expect(result.current.currPeriod).toBe(Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }).substring(0, 7)); - }); + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month' }), + ) + expect(result.current.viewMode).toBe('month') + expect(result.current.currPeriod).toBe( + Temporal.Now.plainDateISO() + .toString({ calendarName: 'auto' }) + ) + }) test('should navigate to the previous period correctly', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month' }), + ) act(() => { - result.current.getPrev(mockEvent); - }); + result.current.getPrev(mockEvent) + }) - const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ months: 1 }); - expect(result.current.currPeriod).toBe(expectedPreviousMonth.toString({ calendarName: 'auto' }).substring(0, 7)); - }); + const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ + months: 1, + }) + expect(result.current.currPeriod).toBe( + expectedPreviousMonth.toString({ calendarName: 'auto' }), + ) + }) test('should navigate to the next period correctly', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month' }), + ) act(() => { - result.current.getNext(mockEvent); - }); + result.current.getNext(mockEvent) + }) - const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }); - expect(result.current.currPeriod).toBe(expectedNextMonth.toString({ calendarName: 'auto' }).substring(0, 7)); - }); + const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) + expect(result.current.currPeriod).toBe( + expectedNextMonth.toString({ calendarName: 'auto' }), + ) + }) test('should reset to the current period correctly', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month' }), + ) act(() => { - result.current.getNext(mockEvent); - result.current.getCurrent(mockEvent); - }); + result.current.getNext(mockEvent) + result.current.getCurrent(mockEvent) + }) - expect(result.current.currPeriod).toBe(Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }).substring(0, 7)); - }); + expect(result.current.currPeriod).toBe( + Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }) + ) + }) test('should change view mode correctly', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month' }), + ) act(() => { - result.current.changeViewMode('week'); - }); + result.current.changeViewMode('week') + }) - expect(result.current.viewMode).toBe('week'); - }); + expect(result.current.viewMode).toBe('week') + }) test('should select a day correctly', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' })); + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month' }), + ) act(() => { - result.current.get(Temporal.PlainDate.from('2024-06-01')); - }); + result.current.get(Temporal.PlainDate.from('2024-06-01')) + }) - expect(result.current.currPeriod).toBe('2024-06-01'); - }); + expect(result.current.currPeriod).toBe('2024-06-01') + }) test('should return the correct props for an event', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: 'week' })); + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'week' }), + ) - const eventProps = result.current.getEventProps('1'); + const eventProps = result.current.getEventProps('1') expect(eventProps).toEqual({ style: { position: 'absolute', - top: '41.66666666666667%', + top: 'min(41.66666666666667%, calc(100% - 55px))', left: '2%', width: '96%', margin: 0, - height: '5.555555555555555%', + height: '8.333333333333332%', }, - }); - }); -}); + }) + }) +}) From 5b6533e812b615d8ff4bdfa510149dcb73c3e6a7 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 2 Jun 2024 23:44:06 +0200 Subject: [PATCH 010/128] test: useCalendar tests --- .../react-time/src/tests/useCalendar.test.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.ts b/packages/react-time/src/tests/useCalendar.test.ts index c808afd..e570172 100644 --- a/packages/react-time/src/tests/useCalendar.test.ts +++ b/packages/react-time/src/tests/useCalendar.test.ts @@ -41,12 +41,14 @@ describe('useCalendar', () => { result.current.getPrev(mockEvent) }) - const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ - months: 1, - }) - expect(result.current.currPeriod).toBe( - expectedPreviousMonth.toString({ calendarName: 'auto' }), - ) + const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ months: 1 }); + const expectedFirstDayOfPreviousMonth = Temporal.PlainDate.from({ + year: expectedPreviousMonth.year, + month: expectedPreviousMonth.month, + day: 1, + }); + + expect(result.current.firstDayOfPeriod).toEqual(expectedFirstDayOfPreviousMonth); }) test('should navigate to the next period correctly', () => { @@ -58,10 +60,14 @@ describe('useCalendar', () => { result.current.getNext(mockEvent) }) - const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) - expect(result.current.currPeriod).toBe( - expectedNextMonth.toString({ calendarName: 'auto' }), - ) + const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }); + const expectedFirstDayOfNextMonth = Temporal.PlainDate.from({ + year: expectedNextMonth.year, + month: expectedNextMonth.month, + day: 1, + }); + + expect(result.current.firstDayOfPeriod).toEqual(expectedFirstDayOfNextMonth); }) test('should reset to the current period correctly', () => { From 37effd4e7746d8595f6fa6b9e7f71fc85f800ae1 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 3 Jun 2024 00:12:17 +0200 Subject: [PATCH 011/128] test: overlaping events --- .../react-time/src/tests/useCalendar.test.ts | 66 ++++++++++++++++--- packages/react-time/src/useCalendar.ts | 12 ++-- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.ts b/packages/react-time/src/tests/useCalendar.test.ts index e570172..153db8e 100644 --- a/packages/react-time/src/tests/useCalendar.test.ts +++ b/packages/react-time/src/tests/useCalendar.test.ts @@ -27,8 +27,7 @@ describe('useCalendar', () => { ) expect(result.current.viewMode).toBe('month') expect(result.current.currPeriod).toBe( - Temporal.Now.plainDateISO() - .toString({ calendarName: 'auto' }) + Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }), ) }) @@ -41,14 +40,18 @@ describe('useCalendar', () => { result.current.getPrev(mockEvent) }) - const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ months: 1 }); + const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ + months: 1, + }) const expectedFirstDayOfPreviousMonth = Temporal.PlainDate.from({ year: expectedPreviousMonth.year, month: expectedPreviousMonth.month, day: 1, - }); + }) - expect(result.current.firstDayOfPeriod).toEqual(expectedFirstDayOfPreviousMonth); + expect(result.current.firstDayOfPeriod).toEqual( + expectedFirstDayOfPreviousMonth, + ) }) test('should navigate to the next period correctly', () => { @@ -60,14 +63,14 @@ describe('useCalendar', () => { result.current.getNext(mockEvent) }) - const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }); + const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) const expectedFirstDayOfNextMonth = Temporal.PlainDate.from({ year: expectedNextMonth.year, month: expectedNextMonth.month, day: 1, - }); + }) - expect(result.current.firstDayOfPeriod).toEqual(expectedFirstDayOfNextMonth); + expect(result.current.firstDayOfPeriod).toEqual(expectedFirstDayOfNextMonth) }) test('should reset to the current period correctly', () => { @@ -81,7 +84,7 @@ describe('useCalendar', () => { }) expect(result.current.currPeriod).toBe( - Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }) + Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }), ) }) @@ -127,4 +130,49 @@ describe('useCalendar', () => { }, }) }) + + test('should return the correct props for overlapping events', () => { + const overlappingEvents = [ + { + id: '1', + startDate: Temporal.PlainDateTime.from('2024-06-01T10:00:00'), + endDate: Temporal.PlainDateTime.from('2024-06-01T12:00:00'), + title: 'Event 1', + }, + { + id: '2', + startDate: Temporal.PlainDateTime.from('2024-06-01T11:00:00'), + endDate: Temporal.PlainDateTime.from('2024-06-01T13:00:00'), + title: 'Event 2', + }, + ] + const { result } = renderHook(() => + useCalendar({ events: overlappingEvents, viewMode: 'week' }), + ) + + const event1Props = result.current.getEventProps('1') + const event2Props = result.current.getEventProps('2') + + expect(event1Props).toEqual({ + style: { + position: 'absolute', + top: 'min(41.66666666666667%, calc(100% - 55px))', + left: '2%', + width: '47%', + margin: 0, + height: '8.333333333333332%', + }, + }) + + expect(event2Props).toEqual({ + style: { + position: 'absolute', + top: 'min(45.83333333333333%, calc(100% - 55px))', + left: '51%', + width: '47%', + margin: 0, + height: '8.333333333333332%', + }, + }) + }) }) diff --git a/packages/react-time/src/useCalendar.ts b/packages/react-time/src/useCalendar.ts index edfdf18..22fbf4c 100644 --- a/packages/react-time/src/useCalendar.ts +++ b/packages/react-time/src/useCalendar.ts @@ -153,16 +153,16 @@ export const useCalendar = ({ const splitEvents = splitMultiDayEvents(event) splitEvents.forEach((splitEvent) => { const splitKey = splitEvent.startDate.toString().split('T')[0] - if (splitKey && !map.has(splitKey)) { - map.set(splitKey, []) - map.get(splitKey)?.push(splitEvent) + if (splitKey) { + if (!map.has(splitKey)) map.set(splitKey, []) + map.get(splitKey)?.push(splitEvent) } }) } else { const eventKey = event.startDate.toString().split('T')[0] - if (eventKey && !map.has(eventKey)) { - map.set(eventKey, []) - map.get(eventKey)?.push(event) + if (eventKey) { + if (!map.has(eventKey)) map.set(eventKey, []) + map.get(eventKey)?.push(event) } } }) From 096f95d82d64c964dddaf59fee79b8c694fc0433 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 3 Jun 2024 16:36:36 +0200 Subject: [PATCH 012/128] feat(react-time/useCalendar): getCurrentTimeMarkerProps --- docs/framework/react/reference/uesCalendar.md | 2 ++ .../react-time/src/tests/useCalendar.test.ts | 23 +++++++++++++++- packages/react-time/src/useCalendar.ts | 27 ++++++++++++++++++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/docs/framework/react/reference/uesCalendar.md b/docs/framework/react/reference/uesCalendar.md index 91a0650..4fbf0aa 100644 --- a/docs/framework/react/reference/uesCalendar.md +++ b/docs/framework/react/reference/uesCalendar.md @@ -62,6 +62,8 @@ export function useCalendar({ - This function is used to retrieve the style properties for a specific event based on its ID. - `getEventProps: (id: string) => { style: CSSProperties } | null` - This function is used to retrieve the style properties for a specific event based on its ID. +- `getCurrentTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` + - This function is used to retrieve the style properties and current time for the current time marker. #### Example Usage diff --git a/packages/react-time/src/tests/useCalendar.test.ts b/packages/react-time/src/tests/useCalendar.test.ts index 153db8e..2c29fe5 100644 --- a/packages/react-time/src/tests/useCalendar.test.ts +++ b/packages/react-time/src/tests/useCalendar.test.ts @@ -1,5 +1,5 @@ import { Temporal } from '@js-temporal/polyfill' -import { describe, expect, test } from 'vitest' +import { describe, expect, test, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' import { useCalendar } from '../useCalendar' @@ -175,4 +175,25 @@ describe('useCalendar', () => { }, }) }) + + test('should return the correct props for the current time marker', () => { + vi.setSystemTime(new Date('2024-06-01T11:00:00')); + + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'week' }), + ) + + const currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps() + + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.83333333333333%', + left: '0%', + }, + currentTime: '11:00', + }) + + vi.useRealTimers(); + }) }) diff --git a/packages/react-time/src/useCalendar.ts b/packages/react-time/src/useCalendar.ts index 22fbf4c..1bc7b87 100644 --- a/packages/react-time/src/useCalendar.ts +++ b/packages/react-time/src/useCalendar.ts @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { Temporal } from '@js-temporal/polyfill' import type { CSSProperties, MouseEventHandler } from 'react' @@ -101,6 +101,7 @@ export const useCalendar = ({ const [currPeriod, setCurrPeriod] = useState(today) const [viewMode, setViewMode] = useState(initialViewMode) + const [currentTime, setCurrentTime] = useState(Temporal.Now.plainDateTimeISO()); const firstDayOfMonth = getFirstDayOfMonth( currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), @@ -327,6 +328,29 @@ export const useCalendar = ({ [eventMap, viewMode], ) + useEffect(() => { + const intervalId = setInterval(() => { + setCurrentTime(Temporal.Now.plainDateTimeISO()); + }, 60000); + + return () => clearInterval(intervalId); + }, []); + + const getCurrentTimeMarkerProps = useCallback(() => { + const { hour, minute } = currentTime; + const currentTimeInMinutes = hour * 60 + minute; + const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100; + + return { + style: { + position: 'absolute', + top: `${percentageOfDay}%`, + left: 0, + }, + currentTime: currentTime.toString().split('T')[1]?.substring(0, 5), + } + }, [currentTime]); + return { firstDayOfPeriod: viewMode === 'month' @@ -346,5 +370,6 @@ export const useCalendar = ({ viewMode, changeViewMode, getEventProps, + getCurrentTimeMarkerProps, } } From 26b0e1e675d44bf914867419a3c9e9e8f992f381 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 3 Jun 2024 16:43:17 +0200 Subject: [PATCH 013/128] feat(react-time/useCalendar): getCurrentTimeMarkerProps --- docs/framework/react/reference/uesCalendar.md | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/docs/framework/react/reference/uesCalendar.md b/docs/framework/react/reference/uesCalendar.md index 4fbf0aa..60563c1 100644 --- a/docs/framework/react/reference/uesCalendar.md +++ b/docs/framework/react/reference/uesCalendar.md @@ -82,6 +82,7 @@ const CalendarComponent = ({ events }) => { daysNames, viewMode, getEventProps, + getCurrentTimeMarkerProps, } = useCalendar({ events, viewMode: 'month', @@ -102,21 +103,23 @@ const CalendarComponent = ({ events }) => { -
+ {viewMode === 'month' && ( -
- {daysNames.map((dayName, index) => ( -
- {dayName} -
- ))} -
+ + + {daysNames.map((dayName, index) => ( + + ))} + + )} -
+
{chunks.map((week, weekIndex) => ( -
+
{week.map((day) => ( -
+
))} - +
+ ))} - - + +
+ {dayName} +
{day.date.day}
@@ -131,12 +134,13 @@ const CalendarComponent = ({ events }) => { ))} - +
); }; From 3cf89c309fadcaef581fdb93e9eb6ad7f955a1dd Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 3 Jun 2024 16:47:09 +0200 Subject: [PATCH 014/128] feat(react-time/useCalendar): getCurrentTimeMarkerProps --- docs/framework/react/reference/uesCalendar.md | 2 +- packages/react-time/src/useCalendar.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/framework/react/reference/uesCalendar.md b/docs/framework/react/reference/uesCalendar.md index 60563c1..3ac395a 100644 --- a/docs/framework/react/reference/uesCalendar.md +++ b/docs/framework/react/reference/uesCalendar.md @@ -144,4 +144,4 @@ const CalendarComponent = ({ events }) => { ); }; -``` \ No newline at end of file +``` diff --git a/packages/react-time/src/useCalendar.ts b/packages/react-time/src/useCalendar.ts index 1bc7b87..5d14ba4 100644 --- a/packages/react-time/src/useCalendar.ts +++ b/packages/react-time/src/useCalendar.ts @@ -243,6 +243,9 @@ export const useCalendar = ({ const getEventProps = useCallback( (id: Event['id']): { style: CSSProperties } | null => { + // TODO: Drag and drop events + // TODO: Change event duration by dragging + const event = [...eventMap.values()] .flat() .find((currEvent) => currEvent.id === id) From 4ae3c4547b0821f81a8529e0629af26adc68832c Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 3 Jun 2024 22:59:12 +0200 Subject: [PATCH 015/128] refactor: reducer --- packages/react-time/package.json | 1 + .../react-time/src/tests/useCalendar.test.ts | 2 +- .../src/useCalendar/calendarActions.ts | 10 ++ packages/react-time/src/useCalendar/index.ts | 1 + .../src/{ => useCalendar}/useCalendar.ts | 156 ++++++++++-------- .../src/useCalendar/useCalendarReducer.ts | 29 ++++ .../src/useCalendar/useCalendarState.ts | 14 ++ 7 files changed, 144 insertions(+), 69 deletions(-) create mode 100644 packages/react-time/src/useCalendar/calendarActions.ts create mode 100644 packages/react-time/src/useCalendar/index.ts rename packages/react-time/src/{ => useCalendar}/useCalendar.ts (76%) create mode 100644 packages/react-time/src/useCalendar/useCalendarReducer.ts create mode 100644 packages/react-time/src/useCalendar/useCalendarState.ts diff --git a/packages/react-time/package.json b/packages/react-time/package.json index a784c85..0181e8d 100644 --- a/packages/react-time/package.json +++ b/packages/react-time/package.json @@ -64,6 +64,7 @@ "dependencies": { "@js-temporal/polyfill": "^0.4.4", "@tanstack/time": "workspace:*", + "typesafe-actions": "^5.1.0", "use-sync-external-store": "^1.2.0" }, "devDependencies": { diff --git a/packages/react-time/src/tests/useCalendar.test.ts b/packages/react-time/src/tests/useCalendar.test.ts index 2c29fe5..a425434 100644 --- a/packages/react-time/src/tests/useCalendar.test.ts +++ b/packages/react-time/src/tests/useCalendar.test.ts @@ -189,7 +189,7 @@ describe('useCalendar', () => { style: { position: 'absolute', top: '45.83333333333333%', - left: '0%', + left: 0, }, currentTime: '11:00', }) diff --git a/packages/react-time/src/useCalendar/calendarActions.ts b/packages/react-time/src/useCalendar/calendarActions.ts new file mode 100644 index 0000000..c713b37 --- /dev/null +++ b/packages/react-time/src/useCalendar/calendarActions.ts @@ -0,0 +1,10 @@ +import { createAction } from 'typesafe-actions'; +import type { Temporal } from '@js-temporal/polyfill'; +import type { ActionType } from 'typesafe-actions'; + +export const setCurrentPeriod = createAction('SET_CURRENT_PERIOD')(); +export const setViewMode = createAction('SET_VIEW_MODE')<'month' | 'week' | number>(); +export const updateCurrentTime = createAction('UPDATE_CURRENT_TIME')(); + +const actions = { setCurrentPeriod, setViewMode, updateCurrentTime }; +export type CalendarAction = ActionType; diff --git a/packages/react-time/src/useCalendar/index.ts b/packages/react-time/src/useCalendar/index.ts new file mode 100644 index 0000000..0b95ae9 --- /dev/null +++ b/packages/react-time/src/useCalendar/index.ts @@ -0,0 +1 @@ +export { useCalendar } from './useCalendar'; diff --git a/packages/react-time/src/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts similarity index 76% rename from packages/react-time/src/useCalendar.ts rename to packages/react-time/src/useCalendar/useCalendar.ts index 5d14ba4..06b9c73 100644 --- a/packages/react-time/src/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,12 +1,20 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo } from 'react' import { Temporal } from '@js-temporal/polyfill' +import { + setCurrentPeriod, + setViewMode, + updateCurrentTime, +} from './calendarActions' +import { useCalendarReducer } from './useCalendarReducer' +import type { Event } from './useCalendarState' import type { CSSProperties, MouseEventHandler } from 'react' -export interface Event { - id: string - startDate: Temporal.PlainDateTime - endDate: Temporal.PlainDateTime - title: string +interface UseCalendarProps { + weekStartsOn?: number + events: Event[] + viewMode: 'month' | 'week' | number + locale?: string + onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void } const getFirstDayOfMonth = (currMonth: string) => @@ -69,14 +77,6 @@ const splitMultiDayEvents = (event: Event) => { return events } -interface UseCalendarProps { - weekStartsOn?: number - events: Event[] - viewMode: 'month' | 'week' | number - locale?: string - onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void -} - const generateDateRange = ( start: Temporal.PlainDate, end: Temporal.PlainDate, @@ -98,18 +98,21 @@ export const useCalendar = ({ onChangeViewMode, }: UseCalendarProps) => { const today = Temporal.Now.plainDateISO() - - const [currPeriod, setCurrPeriod] = useState(today) - const [viewMode, setViewMode] = useState(initialViewMode) - const [currentTime, setCurrentTime] = useState(Temporal.Now.plainDateTimeISO()); - + const [state, dispatch] = useCalendarReducer({ + currPeriod: today, + viewMode: initialViewMode, + currentTime: Temporal.Now.plainDateTimeISO(), + }) const firstDayOfMonth = getFirstDayOfMonth( - currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), + state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), + ) + const firstDayOfWeek = getFirstDayOfWeek( + state.currPeriod.toString(), + weekStartsOn, ) - const firstDayOfWeek = getFirstDayOfWeek(currPeriod.toString(), weekStartsOn) const days = - viewMode === 'month' + state.viewMode === 'month' ? Array.from( getChunks( generateDateRange( @@ -119,7 +122,7 @@ export const useCalendar = ({ 7, ), ) - : viewMode === 'week' + : state.viewMode === 'week' ? Array.from( getChunks( generateDateRange( @@ -132,10 +135,10 @@ export const useCalendar = ({ : Array.from( getChunks( generateDateRange( - currPeriod, - currPeriod.add({ days: viewMode - 1 }), + state.currPeriod, + state.currPeriod.add({ days: state.viewMode - 1 }), ), - viewMode, + state.viewMode, ), ) @@ -156,14 +159,14 @@ export const useCalendar = ({ const splitKey = splitEvent.startDate.toString().split('T')[0] if (splitKey) { if (!map.has(splitKey)) map.set(splitKey, []) - map.get(splitKey)?.push(splitEvent) + map.get(splitKey)?.push(splitEvent) } }) } else { const eventKey = event.startDate.toString().split('T')[0] if (eventKey) { if (!map.has(eventKey)) map.set(eventKey, []) - map.get(eventKey)?.push(event) + map.get(eventKey)?.push(event) } } }) @@ -184,68 +187,85 @@ export const useCalendar = ({ }) const getPrev = useCallback>(() => { - switch (viewMode) { + switch (state.viewMode) { case 'month': { const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: 1 }) - setCurrPeriod(firstDayOfPrevMonth) + dispatch(setCurrentPeriod(firstDayOfPrevMonth)) break } case 'week': { const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: 1 }) - setCurrPeriod(firstDayOfPrevWeek) + dispatch(setCurrentPeriod(firstDayOfPrevWeek)) break } default: { - const prevCustomStart = currPeriod.subtract({ days: viewMode }) - setCurrPeriod(prevCustomStart) + const prevCustomStart = state.currPeriod.subtract({ + days: state.viewMode, + }) + dispatch(setCurrentPeriod(prevCustomStart)) break } } - }, [viewMode, firstDayOfMonth, firstDayOfWeek, currPeriod]) + }, [ + state.viewMode, + state.currPeriod, + firstDayOfMonth, + dispatch, + firstDayOfWeek, + ]) const getNext = useCallback>(() => { - switch (viewMode) { + switch (state.viewMode) { case 'month': { const firstDayOfNextMonth = firstDayOfMonth.add({ months: 1 }) - setCurrPeriod(firstDayOfNextMonth) + dispatch(setCurrentPeriod(firstDayOfNextMonth)) break } case 'week': { const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: 1 }) - setCurrPeriod(firstDayOfNextWeek) + dispatch(setCurrentPeriod(firstDayOfNextWeek)) break } default: { - const nextCustomStart = currPeriod.add({ days: viewMode }) - setCurrPeriod(nextCustomStart) + const nextCustomStart = state.currPeriod.add({ days: state.viewMode }) + dispatch(setCurrentPeriod(nextCustomStart)) break } } - }, [viewMode, firstDayOfMonth, firstDayOfWeek, currPeriod]) + }, [ + state.viewMode, + state.currPeriod, + firstDayOfMonth, + dispatch, + firstDayOfWeek, + ]) const getCurrent = useCallback>(() => { - setCurrPeriod(today) - }, [today]) + dispatch(setCurrentPeriod(Temporal.Now.plainDateISO())) + }, [dispatch]) - const get = useCallback((date: Temporal.PlainDate) => { - setCurrPeriod(date) - }, []) + const get = useCallback( + (date: Temporal.PlainDate) => { + dispatch(setCurrentPeriod(date)) + }, + [dispatch], + ) const chunks = - viewMode === 'month' ? [...getChunks(daysWithEvents, 7)] : [daysWithEvents] + state.viewMode === 'month' + ? [...getChunks(daysWithEvents, 7)] + : [daysWithEvents] + const changeViewMode = useCallback( (newViewMode: 'month' | 'week' | number) => { onChangeViewMode?.(newViewMode) - setViewMode(newViewMode) + dispatch(setViewMode(newViewMode)) }, - [onChangeViewMode], + [dispatch, onChangeViewMode], ) const getEventProps = useCallback( (id: Event['id']): { style: CSSProperties } | null => { - // TODO: Drag and drop events - // TODO: Change event duration by dragging - const event = [...eventMap.values()] .flat() .find((currEvent) => currEvent.id === id) @@ -313,7 +333,7 @@ export const useCalendar = ({ : 100 - 2 * sidePadding const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) - if (viewMode === 'week' || typeof viewMode === 'number') { + if (state.viewMode === 'week' || typeof state.viewMode === 'number') { return { style: { position: 'absolute', @@ -328,21 +348,21 @@ export const useCalendar = ({ return null }, - [eventMap, viewMode], + [eventMap, state.viewMode], ) useEffect(() => { const intervalId = setInterval(() => { - setCurrentTime(Temporal.Now.plainDateTimeISO()); - }, 60000); - - return () => clearInterval(intervalId); - }, []); + dispatch(updateCurrentTime(Temporal.Now.plainDateTimeISO())) + }, 60000) + + return () => clearInterval(intervalId) + }, [dispatch]) const getCurrentTimeMarkerProps = useCallback(() => { - const { hour, minute } = currentTime; - const currentTimeInMinutes = hour * 60 + minute; - const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100; + const { hour, minute } = state.currentTime + const currentTimeInMinutes = hour * 60 + minute + const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 return { style: { @@ -350,18 +370,18 @@ export const useCalendar = ({ top: `${percentageOfDay}%`, left: 0, }, - currentTime: currentTime.toString().split('T')[1]?.substring(0, 5), + currentTime: state.currentTime.toString().split('T')[1]?.substring(0, 5), } - }, [currentTime]); + }, [state.currentTime]) return { firstDayOfPeriod: - viewMode === 'month' + state.viewMode === 'month' ? firstDayOfMonth - : viewMode === 'week' + : state.viewMode === 'week' ? firstDayOfWeek - : currPeriod, - currPeriod: currPeriod.toString({ calendarName: 'auto' }), + : state.currPeriod, + currPeriod: state.currPeriod.toString({ calendarName: 'auto' }), getPrev, getNext, getCurrent, @@ -370,7 +390,7 @@ export const useCalendar = ({ daysNames: days .flat() .map((day) => day.toLocaleString(locale, { weekday: 'short' })), - viewMode, + viewMode: state.viewMode, changeViewMode, getEventProps, getCurrentTimeMarkerProps, diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts new file mode 100644 index 0000000..06391a4 --- /dev/null +++ b/packages/react-time/src/useCalendar/useCalendarReducer.ts @@ -0,0 +1,29 @@ +import { useReducer } from 'react'; +import { createReducer } from 'typesafe-actions'; + +import { setCurrentPeriod, setViewMode, updateCurrentTime } from './calendarActions'; +import type { CalendarAction} from './calendarActions'; +import type { CalendarState} from './useCalendarState'; + +const createCalendarReducer = (initialState: CalendarState) => { + return createReducer(initialState) + .handleAction(setCurrentPeriod, (state, action) => ({ + ...state, + currPeriod: action.payload, + })) + .handleAction(setViewMode, (state, action) => ({ + ...state, + viewMode: action.payload, + })) + .handleAction(updateCurrentTime, (state, action) => ({ + ...state, + currentTime: action.payload, + })); +} + +export const useCalendarReducer = ( + initialState: TState, +) => { + const reducer = createCalendarReducer(initialState); + return useReducer(reducer, initialState); +} diff --git a/packages/react-time/src/useCalendar/useCalendarState.ts b/packages/react-time/src/useCalendar/useCalendarState.ts new file mode 100644 index 0000000..4f7eb91 --- /dev/null +++ b/packages/react-time/src/useCalendar/useCalendarState.ts @@ -0,0 +1,14 @@ +import type { Temporal } from '@js-temporal/polyfill'; + +export interface Event { + id: string; + startDate: Temporal.PlainDateTime; + endDate: Temporal.PlainDateTime; + title: string; +} + +export interface CalendarState { + currPeriod: Temporal.PlainDate; + viewMode: 'month' | 'week' | number; + currentTime: Temporal.PlainDateTime; +} From 4009dae1481028b0bb81b3f9e4df10f2c981f152 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 3 Jun 2024 22:59:20 +0200 Subject: [PATCH 016/128] refactor: reducer --- pnpm-lock.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e032657..8df55dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -162,6 +162,9 @@ importers: react-dom: specifier: ^17.0.0 || ^18.0.0 version: 18.2.0(react@18.2.0) + typesafe-actions: + specifier: ^5.1.0 + version: 5.1.0 use-sync-external-store: specifier: ^1.2.0 version: 1.2.0(react@18.2.0) @@ -10838,6 +10841,11 @@ packages: resolution: {integrity: sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==} dev: true + /typesafe-actions@5.1.0: + resolution: {integrity: sha512-bna6Yi1pRznoo6Bz1cE6btB/Yy8Xywytyfrzu/wc+NFW3ZF0I+2iCGImhBsoYYCOWuICtRO4yHcnDlzgo1AdNg==} + engines: {node: '>= 4'} + dev: false + /typescript@4.9.3: resolution: {integrity: sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==} engines: {node: '>=4.2.0'} From fa3716a5c06882540739e797211c36a9627d5e02 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 4 Jun 2024 11:52:33 +0200 Subject: [PATCH 017/128] refactor(useCalendar): types --- packages/react-time/src/useCalendar/useCalendar.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 06b9c73..2f6b300 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -9,9 +9,9 @@ import { useCalendarReducer } from './useCalendarReducer' import type { Event } from './useCalendarState' import type { CSSProperties, MouseEventHandler } from 'react' -interface UseCalendarProps { +interface UseCalendarProps { weekStartsOn?: number - events: Event[] + events: TEvent[] viewMode: 'month' | 'week' | number locale?: string onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void @@ -31,10 +31,10 @@ const getChunks = function* (arr: T[], n: number) { } } -const splitMultiDayEvents = (event: Event) => { +const splitMultiDayEvents = (event: TEvent): TEvent[] => { const startDate = Temporal.PlainDateTime.from(event.startDate) const endDate = Temporal.PlainDateTime.from(event.endDate) - const events: Event[] = [] + const events: TEvent[] = [] let currentDay = startDate while ( @@ -90,13 +90,13 @@ const generateDateRange = ( return dates } -export const useCalendar = ({ +export const useCalendar = ({ weekStartsOn = 1, events, viewMode: initialViewMode, locale, onChangeViewMode, -}: UseCalendarProps) => { +}: UseCalendarProps) => { const today = Temporal.Now.plainDateISO() const [state, dispatch] = useCalendarReducer({ currPeriod: today, @@ -143,7 +143,7 @@ export const useCalendar = ({ ) const eventMap = useMemo(() => { - const map = new Map() + const map = new Map() events.forEach((event) => { const eventStartDate = Temporal.PlainDateTime.from(event.startDate) From 131984b16eb26d26b810a2093f9a066c3958a164 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 4 Jun 2024 12:04:35 +0200 Subject: [PATCH 018/128] refactor(useCalendar): types --- packages/react-time/src/useCalendar/useCalendar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 2f6b300..89ae6b8 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -13,7 +13,7 @@ interface UseCalendarProps { weekStartsOn?: number events: TEvent[] viewMode: 'month' | 'week' | number - locale?: string + locale?: Parameters['0'] onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void } From e54293462cadfd4fa09d65b294d85e69f12b2ec7 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 4 Jun 2024 22:37:59 +0200 Subject: [PATCH 019/128] refactor: useCalendar reducer --- docs/framework/react/reference/uesCalendar.md | 24 ++--- .../react-time/src/tests/useCalendar.test.ts | 10 +- .../src/useCalendar/calendarActions.ts | 10 +- .../react-time/src/useCalendar/useCalendar.ts | 100 ++++-------------- .../src/useCalendar/useCalendarReducer.ts | 94 +++++++++++++--- .../src/useCalendar/useCalendarState.ts | 1 + packages/time/package.json | 5 +- packages/time/src/index.ts | 2 +- packages/time/src/utils/getFirstDayOfMonth.ts | 4 + packages/time/src/utils/getFirstDayOfWeek.ts | 6 ++ packages/time/src/utils/index.ts | 3 + 11 files changed, 147 insertions(+), 112 deletions(-) create mode 100644 packages/time/src/utils/getFirstDayOfMonth.ts create mode 100644 packages/time/src/utils/getFirstDayOfWeek.ts create mode 100644 packages/time/src/utils/index.ts diff --git a/docs/framework/react/reference/uesCalendar.md b/docs/framework/react/reference/uesCalendar.md index 3ac395a..7bf0a04 100644 --- a/docs/framework/react/reference/uesCalendar.md +++ b/docs/framework/react/reference/uesCalendar.md @@ -40,11 +40,11 @@ export function useCalendar({ - This value represents the first day of the current period displayed by the calendar. - `currPeriod: string` - This value represents a string that describes the current period displayed by the calendar. -- `getPrev: MouseEventHandler` +- `setPreviousPeriod: MouseEventHandler` - This function is a click event handler that navigates to the previous period. -- `getNext: MouseEventHandler` +- `setNextPeriod: MouseEventHandler` - This function is a click event handler that navigates to the next period. -- `getCurrent: MouseEventHandler` +- `getCurrentPeriod: MouseEventHandler` - This function is a click event handler that navigates to the current period. - `get: (date: Temporal.PlainDate) => void` - This function is a callback function that is called when a date is selected in the calendar. It receives the selected date as an argument. @@ -62,7 +62,7 @@ export function useCalendar({ - This function is used to retrieve the style properties for a specific event based on its ID. - `getEventProps: (id: string) => { style: CSSProperties } | null` - This function is used to retrieve the style properties for a specific event based on its ID. -- `getCurrentTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` +- `getCurrentPeriodTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` - This function is used to retrieve the style properties and current time for the current time marker. @@ -73,16 +73,16 @@ const CalendarComponent = ({ events }) => { const { firstDayOfPeriod, currPeriod, - getPrev, - getNext, - getCurrent, + setPreviousPeriod, + setNextPeriod, + getCurrentPeriod, get, changeViewMode, chunks, daysNames, viewMode, getEventProps, - getCurrentTimeMarkerProps, + getCurrentPeriodTimeMarkerProps, } = useCalendar({ events, viewMode: 'month', @@ -93,9 +93,9 @@ const CalendarComponent = ({ events }) => { return (
- - - + + +
@@ -136,7 +136,7 @@ const CalendarComponent = ({ events }) => {
))} -
+
))} diff --git a/packages/react-time/src/tests/useCalendar.test.ts b/packages/react-time/src/tests/useCalendar.test.ts index a425434..a66a0b7 100644 --- a/packages/react-time/src/tests/useCalendar.test.ts +++ b/packages/react-time/src/tests/useCalendar.test.ts @@ -37,7 +37,7 @@ describe('useCalendar', () => { ) act(() => { - result.current.getPrev(mockEvent) + result.current.setPreviousPeriod(mockEvent) }) const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ @@ -60,7 +60,7 @@ describe('useCalendar', () => { ) act(() => { - result.current.getNext(mockEvent) + result.current.setNextPeriod(mockEvent) }) const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) @@ -79,8 +79,8 @@ describe('useCalendar', () => { ) act(() => { - result.current.getNext(mockEvent) - result.current.getCurrent(mockEvent) + result.current.setNextPeriod(mockEvent) + result.current.getCurrentPeriod(mockEvent) }) expect(result.current.currPeriod).toBe( @@ -183,7 +183,7 @@ describe('useCalendar', () => { useCalendar({ events, viewMode: 'week' }), ) - const currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps() + const currentTimeMarkerProps = result.current.getCurrentPeriodTimeMarkerProps() expect(currentTimeMarkerProps).toEqual({ style: { diff --git a/packages/react-time/src/useCalendar/calendarActions.ts b/packages/react-time/src/useCalendar/calendarActions.ts index c713b37..c9190d5 100644 --- a/packages/react-time/src/useCalendar/calendarActions.ts +++ b/packages/react-time/src/useCalendar/calendarActions.ts @@ -2,9 +2,11 @@ import { createAction } from 'typesafe-actions'; import type { Temporal } from '@js-temporal/polyfill'; import type { ActionType } from 'typesafe-actions'; -export const setCurrentPeriod = createAction('SET_CURRENT_PERIOD')(); -export const setViewMode = createAction('SET_VIEW_MODE')<'month' | 'week' | number>(); -export const updateCurrentTime = createAction('UPDATE_CURRENT_TIME')(); +const setViewMode = createAction('SET_VIEW_MODE')<'month' | 'week' | number>(); +const updateCurrentTime = createAction('UPDATE_CURRENT_TIME')(); +const setCurrentPeriod = createAction('SET_CURRENT_PERIOD')(); +const setNextPeriodPeriod = createAction('SET_NEXT_PERIOD') +const setPreviousPeriod = createAction('SET_PREVIOUS_PERIOD') -const actions = { setCurrentPeriod, setViewMode, updateCurrentTime }; +export const actions = { setCurrentPeriod, setViewMode, updateCurrentTime, setNextPeriodPeriod, setPreviousPeriod }; export type CalendarAction = ActionType; diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 89ae6b8..5b31309 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,10 +1,7 @@ import { useCallback, useEffect, useMemo } from 'react' import { Temporal } from '@js-temporal/polyfill' -import { - setCurrentPeriod, - setViewMode, - updateCurrentTime, -} from './calendarActions' +import { getFirstDayOfMonth, getFirstDayOfWeek } from "@tanstack/time"; +import { actions } from './calendarActions' import { useCalendarReducer } from './useCalendarReducer' import type { Event } from './useCalendarState' import type { CSSProperties, MouseEventHandler } from 'react' @@ -17,14 +14,6 @@ interface UseCalendarProps { onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void } -const getFirstDayOfMonth = (currMonth: string) => - Temporal.PlainDate.from(`${currMonth}-01`) - -const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { - const date = Temporal.PlainDate.from(currWeek) - return date.subtract({ days: (date.dayOfWeek - weekStartsOn + 7) % 7 }) -} - const getChunks = function* (arr: T[], n: number) { for (let i = 0; i < arr.length; i += n) { yield arr.slice(i, i + n) @@ -102,13 +91,16 @@ export const useCalendar = ({ currPeriod: today, viewMode: initialViewMode, currentTime: Temporal.Now.plainDateTimeISO(), + weekStartsOn, }) + const firstDayOfMonth = getFirstDayOfMonth( state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), ) + const firstDayOfWeek = getFirstDayOfWeek( state.currPeriod.toString(), - weekStartsOn, + state.weekStartsOn, ) const days = @@ -186,67 +178,21 @@ export const useCalendar = ({ }) }) - const getPrev = useCallback>(() => { - switch (state.viewMode) { - case 'month': { - const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: 1 }) - dispatch(setCurrentPeriod(firstDayOfPrevMonth)) - break - } - case 'week': { - const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: 1 }) - dispatch(setCurrentPeriod(firstDayOfPrevWeek)) - break - } - default: { - const prevCustomStart = state.currPeriod.subtract({ - days: state.viewMode, - }) - dispatch(setCurrentPeriod(prevCustomStart)) - break - } - } - }, [ - state.viewMode, - state.currPeriod, - firstDayOfMonth, - dispatch, - firstDayOfWeek, - ]) - - const getNext = useCallback>(() => { - switch (state.viewMode) { - case 'month': { - const firstDayOfNextMonth = firstDayOfMonth.add({ months: 1 }) - dispatch(setCurrentPeriod(firstDayOfNextMonth)) - break - } - case 'week': { - const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: 1 }) - dispatch(setCurrentPeriod(firstDayOfNextWeek)) - break - } - default: { - const nextCustomStart = state.currPeriod.add({ days: state.viewMode }) - dispatch(setCurrentPeriod(nextCustomStart)) - break - } - } - }, [ - state.viewMode, - state.currPeriod, - firstDayOfMonth, - dispatch, - firstDayOfWeek, - ]) - - const getCurrent = useCallback>(() => { - dispatch(setCurrentPeriod(Temporal.Now.plainDateISO())) + const setPreviousPeriod = useCallback>(() => { + dispatch(actions.setPreviousPeriod) + }, [dispatch]) + + const setNextPeriod = useCallback>(() => { + dispatch(actions.setNextPeriodPeriod) + }, [dispatch]) + + const getCurrentPeriod = useCallback>(() => { + dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO())) }, [dispatch]) const get = useCallback( (date: Temporal.PlainDate) => { - dispatch(setCurrentPeriod(date)) + dispatch(actions.setCurrentPeriod(date)) }, [dispatch], ) @@ -258,8 +204,8 @@ export const useCalendar = ({ const changeViewMode = useCallback( (newViewMode: 'month' | 'week' | number) => { + dispatch(actions.setViewMode(newViewMode)) onChangeViewMode?.(newViewMode) - dispatch(setViewMode(newViewMode)) }, [dispatch, onChangeViewMode], ) @@ -359,7 +305,7 @@ export const useCalendar = ({ return () => clearInterval(intervalId) }, [dispatch]) - const getCurrentTimeMarkerProps = useCallback(() => { + const getCurrentPeriodTimeMarkerProps = useCallback(() => { const { hour, minute } = state.currentTime const currentTimeInMinutes = hour * 60 + minute const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 @@ -382,9 +328,9 @@ export const useCalendar = ({ ? firstDayOfWeek : state.currPeriod, currPeriod: state.currPeriod.toString({ calendarName: 'auto' }), - getPrev, - getNext, - getCurrent, + setPreviousPeriod, + setNextPeriod, + getCurrentPeriod, get, chunks, daysNames: days @@ -393,6 +339,6 @@ export const useCalendar = ({ viewMode: state.viewMode, changeViewMode, getEventProps, - getCurrentTimeMarkerProps, + getCurrentPeriodTimeMarkerProps, } } diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts index 06391a4..81efe8f 100644 --- a/packages/react-time/src/useCalendar/useCalendarReducer.ts +++ b/packages/react-time/src/useCalendar/useCalendarReducer.ts @@ -1,29 +1,99 @@ -import { useReducer } from 'react'; -import { createReducer } from 'typesafe-actions'; +import { useReducer } from 'react' +import { createReducer } from 'typesafe-actions' -import { setCurrentPeriod, setViewMode, updateCurrentTime } from './calendarActions'; -import type { CalendarAction} from './calendarActions'; -import type { CalendarState} from './useCalendarState'; +import { getFirstDayOfMonth, getFirstDayOfWeek } from '@tanstack/time' +import { type CalendarAction, actions } from './calendarActions' +import type { CalendarState } from './useCalendarState' const createCalendarReducer = (initialState: CalendarState) => { return createReducer(initialState) - .handleAction(setCurrentPeriod, (state, action) => ({ + .handleAction(actions.setCurrentPeriod, (state, action) => ({ ...state, currPeriod: action.payload, })) - .handleAction(setViewMode, (state, action) => ({ + .handleAction(actions.setViewMode, (state, action) => ({ ...state, viewMode: action.payload, })) - .handleAction(updateCurrentTime, (state, action) => ({ + .handleAction(actions.updateCurrentTime, (state, action) => ({ ...state, currentTime: action.payload, - })); + })) + .handleAction(actions.setPreviousPeriod, (state) => { + const firstDayOfMonth = getFirstDayOfMonth( + state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), + ) + const firstDayOfWeek = getFirstDayOfWeek( + state.currPeriod.toString(), + state.weekStartsOn, + ) + + switch (state.viewMode) { + case 'month': { + const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: 1 }) + return { + ...state, + currPeriod: firstDayOfPrevMonth, + } + } + case 'week': { + const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: 1 }) + return { + ...state, + currPeriod: firstDayOfPrevWeek, + } + } + default: { + const prevCustomStart = state.currPeriod.subtract({ + days: state.viewMode, + }) + return { + ...state, + currPeriod: prevCustomStart, + } + } + } + }) + .handleAction(actions.setNextPeriodPeriod, (state, action) => { + const firstDayOfMonth = getFirstDayOfMonth( + state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), + ) + const firstDayOfWeek = getFirstDayOfWeek( + state.currPeriod.toString(), + state.weekStartsOn, + ) + + switch (state.viewMode) { + case 'month': { + const firstDayOfNextMonth = firstDayOfMonth.add({ months: 1 }) + return { + ...state, + currPeriod: firstDayOfNextMonth, + } + } + case 'week': { + const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: 1 }) + return { + ...state, + currPeriod: firstDayOfNextWeek, + } + } + default: { + const nextCustomStart = state.currPeriod.add({ days: state.viewMode }) + return { + ...state, + currPeriod: nextCustomStart, + } + } + } + }) } -export const useCalendarReducer = ( +export const useCalendarReducer = < + TState extends CalendarState = CalendarState, +>( initialState: TState, ) => { - const reducer = createCalendarReducer(initialState); - return useReducer(reducer, initialState); + const reducer = createCalendarReducer(initialState) + return useReducer(reducer, initialState) } diff --git a/packages/react-time/src/useCalendar/useCalendarState.ts b/packages/react-time/src/useCalendar/useCalendarState.ts index 4f7eb91..cd57a88 100644 --- a/packages/react-time/src/useCalendar/useCalendarState.ts +++ b/packages/react-time/src/useCalendar/useCalendarState.ts @@ -11,4 +11,5 @@ export interface CalendarState { currPeriod: Temporal.PlainDate; viewMode: 'month' | 'week' | number; currentTime: Temporal.PlainDateTime; + weekStartsOn: number; } diff --git a/packages/time/package.json b/packages/time/package.json index 081903b..1b37016 100644 --- a/packages/time/package.json +++ b/packages/time/package.json @@ -55,5 +55,8 @@ "files": [ "dist", "src" - ] + ], + "dependencies": { + "@js-temporal/polyfill": "^0.4.4" + } } diff --git a/packages/time/src/index.ts b/packages/time/src/index.ts index 12981a2..0e36b7b 100644 --- a/packages/time/src/index.ts +++ b/packages/time/src/index.ts @@ -1,4 +1,4 @@ /** * TanStack Time */ -export * from './utils/parse'; \ No newline at end of file +export * from './utils'; \ No newline at end of file diff --git a/packages/time/src/utils/getFirstDayOfMonth.ts b/packages/time/src/utils/getFirstDayOfMonth.ts new file mode 100644 index 0000000..845b49b --- /dev/null +++ b/packages/time/src/utils/getFirstDayOfMonth.ts @@ -0,0 +1,4 @@ +import { Temporal } from '@js-temporal/polyfill' + +export const getFirstDayOfMonth = (currMonth: string) => + Temporal.PlainDate.from(`${currMonth}-01`) diff --git a/packages/time/src/utils/getFirstDayOfWeek.ts b/packages/time/src/utils/getFirstDayOfWeek.ts new file mode 100644 index 0000000..bb02180 --- /dev/null +++ b/packages/time/src/utils/getFirstDayOfWeek.ts @@ -0,0 +1,6 @@ +import { Temporal } from '@js-temporal/polyfill' + +export const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { + const date = Temporal.PlainDate.from(currWeek) + return date.subtract({ days: (date.dayOfWeek - weekStartsOn + 7) % 7 }) +} diff --git a/packages/time/src/utils/index.ts b/packages/time/src/utils/index.ts new file mode 100644 index 0000000..293009a --- /dev/null +++ b/packages/time/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './parse'; +export * from './getFirstDayOfMonth'; +export * from './getFirstDayOfWeek'; From a1497781695311ecff4074f8c397120e9cc3179f Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 5 Jun 2024 11:05:56 +0200 Subject: [PATCH 020/128] refactor: useCalendar reducer --- packages/react-time/src/useCalendar/calendarActions.ts | 8 ++++---- packages/react-time/src/useCalendar/useCalendar.ts | 6 +++--- packages/react-time/src/useCalendar/useCalendarReducer.ts | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react-time/src/useCalendar/calendarActions.ts b/packages/react-time/src/useCalendar/calendarActions.ts index c9190d5..ddccef7 100644 --- a/packages/react-time/src/useCalendar/calendarActions.ts +++ b/packages/react-time/src/useCalendar/calendarActions.ts @@ -5,8 +5,8 @@ import type { ActionType } from 'typesafe-actions'; const setViewMode = createAction('SET_VIEW_MODE')<'month' | 'week' | number>(); const updateCurrentTime = createAction('UPDATE_CURRENT_TIME')(); const setCurrentPeriod = createAction('SET_CURRENT_PERIOD')(); -const setNextPeriodPeriod = createAction('SET_NEXT_PERIOD') -const setPreviousPeriod = createAction('SET_PREVIOUS_PERIOD') +const setNextPeriod = createAction('SET_NEXT_PERIOD')(); +const setPreviousPeriod = createAction('SET_PREVIOUS_PERIOD')(); -export const actions = { setCurrentPeriod, setViewMode, updateCurrentTime, setNextPeriodPeriod, setPreviousPeriod }; -export type CalendarAction = ActionType; +export const actions = { setCurrentPeriod, setViewMode, updateCurrentTime, setNextPeriod, setPreviousPeriod }; +export type UseCalendarAction = ActionType; diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 5b31309..e30691d 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -179,11 +179,11 @@ export const useCalendar = ({ }) const setPreviousPeriod = useCallback>(() => { - dispatch(actions.setPreviousPeriod) + dispatch(actions.setPreviousPeriod()) }, [dispatch]) const setNextPeriod = useCallback>(() => { - dispatch(actions.setNextPeriodPeriod) + dispatch(actions.setNextPeriod()); }, [dispatch]) const getCurrentPeriod = useCallback>(() => { @@ -299,7 +299,7 @@ export const useCalendar = ({ useEffect(() => { const intervalId = setInterval(() => { - dispatch(updateCurrentTime(Temporal.Now.plainDateTimeISO())) + dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())) }, 60000) return () => clearInterval(intervalId) diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts index 81efe8f..57fde84 100644 --- a/packages/react-time/src/useCalendar/useCalendarReducer.ts +++ b/packages/react-time/src/useCalendar/useCalendarReducer.ts @@ -2,11 +2,11 @@ import { useReducer } from 'react' import { createReducer } from 'typesafe-actions' import { getFirstDayOfMonth, getFirstDayOfWeek } from '@tanstack/time' -import { type CalendarAction, actions } from './calendarActions' +import { type UseCalendarAction, actions } from './calendarActions' import type { CalendarState } from './useCalendarState' const createCalendarReducer = (initialState: CalendarState) => { - return createReducer(initialState) + return createReducer(initialState) .handleAction(actions.setCurrentPeriod, (state, action) => ({ ...state, currPeriod: action.payload, @@ -54,7 +54,7 @@ const createCalendarReducer = (initialState: CalendarState) => { } } }) - .handleAction(actions.setNextPeriodPeriod, (state, action) => { + .handleAction(actions.setNextPeriod, (state) => { const firstDayOfMonth = getFirstDayOfMonth( state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), ) From 28ac64f8e1ba30f6a83e14c67d5e38d8ce38bf27 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 5 Jun 2024 11:06:07 +0200 Subject: [PATCH 021/128] refactor: useCalendar reducer --- pnpm-lock.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8df55dd..6e5ac1a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,7 +189,11 @@ importers: specifier: ^2.10.1 version: 2.10.1(@testing-library/jest-dom@6.4.2)(solid-js@1.7.8)(vite@5.2.6) - packages/time: {} + packages/time: + dependencies: + '@js-temporal/polyfill': + specifier: ^0.4.4 + version: 0.4.4 packages/vue-time: dependencies: From dfa353a60eef68340366a8faa288331f1ebf2e63 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 5 Jun 2024 12:16:24 +0200 Subject: [PATCH 022/128] feat: add the isToday field --- packages/react-time/src/useCalendar/useCalendar.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index e30691d..5244528 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -174,6 +174,7 @@ export const useCalendar = ({ return { date: day, events: dailyEvents, + isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, } }) }) From 874208741db48acf61e4ef6075cb557b43f1744f Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 5 Jun 2024 12:57:28 +0200 Subject: [PATCH 023/128] feat: add the isToday field --- ...eCalendar.test.ts => useCalendar.test.tsx} | 16 +++++++ .../react-time/src/useCalendar/useCalendar.ts | 44 +++++++++---------- 2 files changed, 37 insertions(+), 23 deletions(-) rename packages/react-time/src/tests/{useCalendar.test.ts => useCalendar.test.tsx} (91%) diff --git a/packages/react-time/src/tests/useCalendar.test.ts b/packages/react-time/src/tests/useCalendar.test.tsx similarity index 91% rename from packages/react-time/src/tests/useCalendar.test.ts rename to packages/react-time/src/tests/useCalendar.test.tsx index a66a0b7..0603e90 100644 --- a/packages/react-time/src/tests/useCalendar.test.ts +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -196,4 +196,20 @@ describe('useCalendar', () => { vi.useRealTimers(); }) + + test('should render array of days', () => { + vi.setSystemTime(new Date('2024-06-01T11:00:00')); + + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + ); + + const { chunks } = result.current; + expect(chunks).toHaveLength(5); + expect(chunks[0]).toHaveLength(7); + + expect(chunks[0]?.[0]?.date.toString()).toBe('2024-06-01'); + expect(chunks[chunks.length - 1]?.[0]?.date.toString()).toBe('2024-06-29'); + expect(chunks[0]?.[0]?.isToday).toBe(true); + }); }) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 5244528..7990026 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -8,7 +8,7 @@ import type { CSSProperties, MouseEventHandler } from 'react' interface UseCalendarProps { weekStartsOn?: number - events: TEvent[] + events?: TEvent[] viewMode: 'month' | 'week' | number locale?: Parameters['0'] onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void @@ -113,7 +113,7 @@ export const useCalendar = ({ ), 7, ), - ) + ).flat() : state.viewMode === 'week' ? Array.from( getChunks( @@ -123,7 +123,7 @@ export const useCalendar = ({ ), 7, ), - ) + ).flat() : Array.from( getChunks( generateDateRange( @@ -132,12 +132,12 @@ export const useCalendar = ({ ), state.viewMode, ), - ) + ).flat() const eventMap = useMemo(() => { const map = new Map() - events.forEach((event) => { + events?.forEach((event) => { const eventStartDate = Temporal.PlainDateTime.from(event.startDate) const eventEndDate = Temporal.PlainDateTime.from(event.endDate) if ( @@ -166,19 +166,22 @@ export const useCalendar = ({ return map }, [events]) - const daysWithEvents = days.map((dayChunk) => { - return dayChunk.map((day) => { - const dayKey = day.toString() - const dailyEvents = eventMap.get(dayKey) ?? [] + const daysWithEvents = days.map((day) => { + const dayKey = day.toString() + const dailyEvents = eventMap.get(dayKey) ?? [] - return { - date: day, - events: dailyEvents, - isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, - } - }) + return { + date: day, + events: dailyEvents, + isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, + } }) + const chunks = + state.viewMode === 'month' + ? [...getChunks(daysWithEvents, 7)] + : [daysWithEvents] + const setPreviousPeriod = useCallback>(() => { dispatch(actions.setPreviousPeriod()) }, [dispatch]) @@ -198,11 +201,6 @@ export const useCalendar = ({ [dispatch], ) - const chunks = - state.viewMode === 'month' - ? [...getChunks(daysWithEvents, 7)] - : [daysWithEvents] - const changeViewMode = useCallback( (newViewMode: 'month' | 'week' | number) => { dispatch(actions.setViewMode(newViewMode)) @@ -334,9 +332,9 @@ export const useCalendar = ({ getCurrentPeriod, get, chunks, - daysNames: days - .flat() - .map((day) => day.toLocaleString(locale, { weekday: 'short' })), + daysNames: Array.from(getChunks(daysWithEvents, 7)).flat() + .slice(0, 7) + .map((day) => day.date.toLocaleString(locale, { weekday: 'short' })), viewMode: state.viewMode, changeViewMode, getEventProps, From 3d6f74a8b75eac107ab44a3299653384fa81441a Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 5 Jun 2024 12:58:46 +0200 Subject: [PATCH 024/128] feat: add the isToday field --- packages/react-time/src/tests/useCalendar.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 0603e90..c390cb8 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -210,6 +210,6 @@ describe('useCalendar', () => { expect(chunks[0]?.[0]?.date.toString()).toBe('2024-06-01'); expect(chunks[chunks.length - 1]?.[0]?.date.toString()).toBe('2024-06-29'); - expect(chunks[0]?.[0]?.isToday).toBe(true); + expect(chunks.find((week) => week.some((day) => day.isToday))?.find((day) => day.isToday)?.date.toString()).toBe('2024-06-01'); }); }) From d93b29d0d56432ff694ff4d7f6cc8bf37cc284c9 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 5 Jun 2024 13:09:58 +0200 Subject: [PATCH 025/128] docs: useCalendar --- .../react/reference/{uesCalendar.md => useCalendar.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename docs/framework/react/reference/{uesCalendar.md => useCalendar.md} (99%) diff --git a/docs/framework/react/reference/uesCalendar.md b/docs/framework/react/reference/useCalendar.md similarity index 99% rename from docs/framework/react/reference/uesCalendar.md rename to docs/framework/react/reference/useCalendar.md index 7bf0a04..2d8fea7 100644 --- a/docs/framework/react/reference/uesCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -136,7 +136,7 @@ const CalendarComponent = ({ events }) => {
))} -
+
))} From 6432973e85d90a4eb1d0485b17cae1b8e1cf2037 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 5 Jun 2024 23:48:55 +0200 Subject: [PATCH 026/128] feat: useCalendar hook --- docs/framework/react/reference/useCalendar.md | 34 +++++++++---------- .../react-time/src/tests/useCalendar.test.tsx | 22 ++++++------ .../src/useCalendar/calendarActions.ts | 6 ++-- .../react-time/src/useCalendar/useCalendar.ts | 28 +++++++-------- .../src/useCalendar/useCalendarReducer.ts | 4 +-- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index 2d8fea7..dcb86e9 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -40,15 +40,15 @@ export function useCalendar({ - This value represents the first day of the current period displayed by the calendar. - `currPeriod: string` - This value represents a string that describes the current period displayed by the calendar. -- `setPreviousPeriod: MouseEventHandler` +- `goToPreviousPeriod: MouseEventHandler` - This function is a click event handler that navigates to the previous period. -- `setNextPeriod: MouseEventHandler` +- `goToNextPeriod: MouseEventHandler` - This function is a click event handler that navigates to the next period. -- `getCurrentPeriod: MouseEventHandler` +- `goToCurrentPeriod: MouseEventHandler` - This function is a click event handler that navigates to the current period. -- `get: (date: Temporal.PlainDate) => void` +- `goToSpecificPeriod: (date: Temporal.PlainDate) => void` - This function is a callback function that is called when a date is selected in the calendar. It receives the selected date as an argument. -- `chunks: Array>` +- `weeks: Array>` - This value represents the calendar grid, where each cell contains the date and events for that day. - `daysNames: string[]` - This value represents an array of strings that contain the names of the days of the week. @@ -62,7 +62,7 @@ export function useCalendar({ - This function is used to retrieve the style properties for a specific event based on its ID. - `getEventProps: (id: string) => { style: CSSProperties } | null` - This function is used to retrieve the style properties for a specific event based on its ID. -- `getCurrentPeriodTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` +- `goToCurrentPeriodTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` - This function is used to retrieve the style properties and current time for the current time marker. @@ -73,16 +73,16 @@ const CalendarComponent = ({ events }) => { const { firstDayOfPeriod, currPeriod, - setPreviousPeriod, - setNextPeriod, - getCurrentPeriod, - get, + goToPreviousPeriod, + goToNextPeriod, + goToCurrentPeriod, + goToSpecificPeriod, changeViewMode, - chunks, + weeks, daysNames, viewMode, getEventProps, - getCurrentPeriodTimeMarkerProps, + goToCurrentPeriodTimeMarkerProps, } = useCalendar({ events, viewMode: 'month', @@ -93,9 +93,9 @@ const CalendarComponent = ({ events }) => { return (
- - - + + +
@@ -116,7 +116,7 @@ const CalendarComponent = ({ events }) => { )} - {chunks.map((week, weekIndex) => ( + {weeks.map((week, weekIndex) => ( {week.map((day) => ( @@ -136,7 +136,7 @@ const CalendarComponent = ({ events }) => {
))} -
+
))} diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index c390cb8..9a73267 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -37,7 +37,7 @@ describe('useCalendar', () => { ) act(() => { - result.current.setPreviousPeriod(mockEvent) + result.current.goToPreviousPeriod(mockEvent) }) const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ @@ -60,7 +60,7 @@ describe('useCalendar', () => { ) act(() => { - result.current.setNextPeriod(mockEvent) + result.current.goToNextPeriod(mockEvent) }) const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) @@ -79,8 +79,8 @@ describe('useCalendar', () => { ) act(() => { - result.current.setNextPeriod(mockEvent) - result.current.getCurrentPeriod(mockEvent) + result.current.goToNextPeriod(mockEvent) + result.current.goToCurrentPeriod(mockEvent) }) expect(result.current.currPeriod).toBe( @@ -183,7 +183,7 @@ describe('useCalendar', () => { useCalendar({ events, viewMode: 'week' }), ) - const currentTimeMarkerProps = result.current.getCurrentPeriodTimeMarkerProps() + const currentTimeMarkerProps = result.current.goToCurrentPeriodTimeMarkerProps() expect(currentTimeMarkerProps).toEqual({ style: { @@ -204,12 +204,12 @@ describe('useCalendar', () => { useCalendar({ events, viewMode: 'month', locale: 'en-US' }) ); - const { chunks } = result.current; - expect(chunks).toHaveLength(5); - expect(chunks[0]).toHaveLength(7); + const { weeks } = result.current; + expect(weeks).toHaveLength(5); + expect(weeks[0]).toHaveLength(7); - expect(chunks[0]?.[0]?.date.toString()).toBe('2024-06-01'); - expect(chunks[chunks.length - 1]?.[0]?.date.toString()).toBe('2024-06-29'); - expect(chunks.find((week) => week.some((day) => day.isToday))?.find((day) => day.isToday)?.date.toString()).toBe('2024-06-01'); + expect(weeks[0]?.[0]?.date.toString()).toBe('2024-06-01'); + expect(weeks[weeks.length - 1]?.[0]?.date.toString()).toBe('2024-06-29'); + expect(weeks.find((week) => week.some((day) => day.isToday))?.find((day) => day.isToday)?.date.toString()).toBe('2024-06-01'); }); }) diff --git a/packages/react-time/src/useCalendar/calendarActions.ts b/packages/react-time/src/useCalendar/calendarActions.ts index ddccef7..aee447f 100644 --- a/packages/react-time/src/useCalendar/calendarActions.ts +++ b/packages/react-time/src/useCalendar/calendarActions.ts @@ -5,8 +5,8 @@ import type { ActionType } from 'typesafe-actions'; const setViewMode = createAction('SET_VIEW_MODE')<'month' | 'week' | number>(); const updateCurrentTime = createAction('UPDATE_CURRENT_TIME')(); const setCurrentPeriod = createAction('SET_CURRENT_PERIOD')(); -const setNextPeriod = createAction('SET_NEXT_PERIOD')(); -const setPreviousPeriod = createAction('SET_PREVIOUS_PERIOD')(); +const goToNextPeriod = createAction('SET_NEXT_PERIOD')(); +const goToPreviousPeriod = createAction('SET_PREVIOUS_PERIOD')(); -export const actions = { setCurrentPeriod, setViewMode, updateCurrentTime, setNextPeriod, setPreviousPeriod }; +export const actions = { setCurrentPeriod, setViewMode, updateCurrentTime, goToNextPeriod, goToPreviousPeriod }; export type UseCalendarAction = ActionType; diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 7990026..f52bbd4 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -177,24 +177,24 @@ export const useCalendar = ({ } }) - const chunks = + const weeks = state.viewMode === 'month' ? [...getChunks(daysWithEvents, 7)] : [daysWithEvents] - const setPreviousPeriod = useCallback>(() => { - dispatch(actions.setPreviousPeriod()) + const goToPreviousPeriod = useCallback>(() => { + dispatch(actions.goToPreviousPeriod()) }, [dispatch]) - const setNextPeriod = useCallback>(() => { - dispatch(actions.setNextPeriod()); + const goToNextPeriod = useCallback>(() => { + dispatch(actions.goToNextPeriod()); }, [dispatch]) - const getCurrentPeriod = useCallback>(() => { + const goToCurrentPeriod = useCallback>(() => { dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO())) }, [dispatch]) - const get = useCallback( + const goToSpecificPeriod = useCallback( (date: Temporal.PlainDate) => { dispatch(actions.setCurrentPeriod(date)) }, @@ -304,7 +304,7 @@ export const useCalendar = ({ return () => clearInterval(intervalId) }, [dispatch]) - const getCurrentPeriodTimeMarkerProps = useCallback(() => { + const goToCurrentPeriodTimeMarkerProps = useCallback(() => { const { hour, minute } = state.currentTime const currentTimeInMinutes = hour * 60 + minute const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 @@ -327,17 +327,17 @@ export const useCalendar = ({ ? firstDayOfWeek : state.currPeriod, currPeriod: state.currPeriod.toString({ calendarName: 'auto' }), - setPreviousPeriod, - setNextPeriod, - getCurrentPeriod, - get, - chunks, + goToPreviousPeriod, + goToNextPeriod, + goToCurrentPeriod, + goToSpecificPeriod, + weeks, daysNames: Array.from(getChunks(daysWithEvents, 7)).flat() .slice(0, 7) .map((day) => day.date.toLocaleString(locale, { weekday: 'short' })), viewMode: state.viewMode, changeViewMode, getEventProps, - getCurrentPeriodTimeMarkerProps, + goToCurrentPeriodTimeMarkerProps, } } diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts index 57fde84..5aa28d2 100644 --- a/packages/react-time/src/useCalendar/useCalendarReducer.ts +++ b/packages/react-time/src/useCalendar/useCalendarReducer.ts @@ -19,7 +19,7 @@ const createCalendarReducer = (initialState: CalendarState) => { ...state, currentTime: action.payload, })) - .handleAction(actions.setPreviousPeriod, (state) => { + .handleAction(actions.goToPreviousPeriod, (state) => { const firstDayOfMonth = getFirstDayOfMonth( state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), ) @@ -54,7 +54,7 @@ const createCalendarReducer = (initialState: CalendarState) => { } } }) - .handleAction(actions.setNextPeriod, (state) => { + .handleAction(actions.goToNextPeriod, (state) => { const firstDayOfMonth = getFirstDayOfMonth( state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), ) From 75a445a6ec0531ed4289c0d80c8268018fe2b916 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 00:00:01 +0200 Subject: [PATCH 027/128] cd --- .../react-time/src/tests/useCalendar.test.tsx | 24 +++++++++++++++---- .../react-time/src/useCalendar/useCalendar.ts | 23 ++++++++++++++---- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 9a73267..1657d40 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -106,7 +106,7 @@ describe('useCalendar', () => { ) act(() => { - result.current.get(Temporal.PlainDate.from('2024-06-01')) + result.current.goToSpecificPeriod(Temporal.PlainDate.from('2024-06-01')) }) expect(result.current.currPeriod).toBe('2024-06-01') @@ -208,8 +208,24 @@ describe('useCalendar', () => { expect(weeks).toHaveLength(5); expect(weeks[0]).toHaveLength(7); - expect(weeks[0]?.[0]?.date.toString()).toBe('2024-06-01'); - expect(weeks[weeks.length - 1]?.[0]?.date.toString()).toBe('2024-06-29'); + expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-27'); + expect(weeks[weeks.length - 1]?.[0]?.date.toString()).toBe('2024-06-24'); expect(weeks.find((week) => week.some((day) => day.isToday))?.find((day) => day.isToday)?.date.toString()).toBe('2024-06-01'); }); -}) + + test('should return the correct day names based on weekStartsOn', () => { + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month', locale: 'en-US', weekStartsOn: 1 }) + ); + + const { daysNames } = result.current; + expect(daysNames).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']); + + const { result: resultSundayStart } = renderHook(() => + useCalendar({ events, viewMode: 'month', locale: 'en-US', weekStartsOn: 7 }) + ); + + const { daysNames: sundayDaysNames } = resultSundayStart.current; + expect(sundayDaysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']); + }); +}); diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index f52bbd4..6a80b1f 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,11 +1,18 @@ import { useCallback, useEffect, useMemo } from 'react' import { Temporal } from '@js-temporal/polyfill' -import { getFirstDayOfMonth, getFirstDayOfWeek } from "@tanstack/time"; import { actions } from './calendarActions' import { useCalendarReducer } from './useCalendarReducer' import type { Event } from './useCalendarState' import type { CSSProperties, MouseEventHandler } from 'react' +export const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { + const date = Temporal.PlainDate.from(currWeek) + return date.subtract({ days: (date.dayOfWeek - weekStartsOn + 7) % 7 }) +} + +export const getFirstDayOfMonth = (currMonth: string) => + Temporal.PlainDate.from(`${currMonth}-01`) + interface UseCalendarProps { weekStartsOn?: number events?: TEvent[] @@ -108,7 +115,7 @@ export const useCalendar = ({ ? Array.from( getChunks( generateDateRange( - firstDayOfMonth, + firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - state.weekStartsOn + 7) % 7 }), firstDayOfMonth.add({ months: 1 }).subtract({ days: 1 }), ), 7, @@ -319,6 +326,14 @@ export const useCalendar = ({ } }, [state.currentTime]) + const daysNames = useMemo(() => { + const baseDate = Temporal.PlainDate.from('2024-01-01') + return Array.from({ length: 7 }).map((_, i) => + baseDate.add({ days: (i + weekStartsOn - 1) % 7 }) + .toLocaleString(locale, { weekday: 'short' }) + ) + }, [locale, weekStartsOn]) + return { firstDayOfPeriod: state.viewMode === 'month' @@ -332,9 +347,7 @@ export const useCalendar = ({ goToCurrentPeriod, goToSpecificPeriod, weeks, - daysNames: Array.from(getChunks(daysWithEvents, 7)).flat() - .slice(0, 7) - .map((day) => day.date.toLocaleString(locale, { weekday: 'short' })), + daysNames, viewMode: state.viewMode, changeViewMode, getEventProps, From 7536efc4c6eefc645a666ed052c4b4597743c8a0 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 00:02:08 +0200 Subject: [PATCH 028/128] feat: useCalendar hook --- packages/react-time/src/tests/useCalendar.test.tsx | 2 +- packages/react-time/src/useCalendar/useCalendar.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 1657d40..5321e76 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -183,7 +183,7 @@ describe('useCalendar', () => { useCalendar({ events, viewMode: 'week' }), ) - const currentTimeMarkerProps = result.current.goToCurrentPeriodTimeMarkerProps() + const currentTimeMarkerProps = result.current.currentTimeMarkerProps() expect(currentTimeMarkerProps).toEqual({ style: { diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 6a80b1f..27e4621 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -311,7 +311,7 @@ export const useCalendar = ({ return () => clearInterval(intervalId) }, [dispatch]) - const goToCurrentPeriodTimeMarkerProps = useCallback(() => { + const currentTimeMarkerProps = useCallback(() => { const { hour, minute } = state.currentTime const currentTimeInMinutes = hour * 60 + minute const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 @@ -351,6 +351,6 @@ export const useCalendar = ({ viewMode: state.viewMode, changeViewMode, getEventProps, - goToCurrentPeriodTimeMarkerProps, + currentTimeMarkerProps, } } From 29d20ae54430ce2d8274ab05eff0bcf9f4356f82 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 00:02:18 +0200 Subject: [PATCH 029/128] feat: useCalendar hook --- docs/framework/react/reference/useCalendar.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index dcb86e9..6a1941f 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -62,7 +62,7 @@ export function useCalendar({ - This function is used to retrieve the style properties for a specific event based on its ID. - `getEventProps: (id: string) => { style: CSSProperties } | null` - This function is used to retrieve the style properties for a specific event based on its ID. -- `goToCurrentPeriodTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` +- `currentTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` - This function is used to retrieve the style properties and current time for the current time marker. @@ -82,7 +82,7 @@ const CalendarComponent = ({ events }) => { daysNames, viewMode, getEventProps, - goToCurrentPeriodTimeMarkerProps, + currentTimeMarkerProps, } = useCalendar({ events, viewMode: 'month', @@ -136,7 +136,7 @@ const CalendarComponent = ({ events }) => {
))} -
+
))} From e37dbf7f646282d1c6be4000d8cb5191ed74f766 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 00:14:43 +0200 Subject: [PATCH 030/128] feat: useCalendar hook --- .../react-time/src/tests/useCalendar.test.tsx | 26 ++++++++++++++++--- .../react-time/src/useCalendar/useCalendar.ts | 2 ++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 5321e76..e4926ee 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -1,9 +1,13 @@ import { Temporal } from '@js-temporal/polyfill' -import { describe, expect, test, vi } from 'vitest' +import { afterEach, describe, expect, test, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' import { useCalendar } from '../useCalendar' describe('useCalendar', () => { + afterEach(() => { + vi.useRealTimers(); + }); + const events = [ { id: '1', @@ -193,8 +197,6 @@ describe('useCalendar', () => { }, currentTime: '11:00', }) - - vi.useRealTimers(); }) test('should render array of days', () => { @@ -228,4 +230,22 @@ describe('useCalendar', () => { const { daysNames: sundayDaysNames } = resultSundayStart.current; expect(sundayDaysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']); }); + + test('should correctly mark days as in current period', () => { + vi.setSystemTime(new Date('2024-06-01T11:00:00')); + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + ); + + const { weeks } = result.current; + const daysInCurrentPeriod = weeks.flat().map(day => day.isInCurrentPeriod); + + expect(daysInCurrentPeriod).toEqual([ + false, false, false, false, false, true, true, + true, true, true, true, true, true, true, + true, true, true, true, true, true, true, + true, true, true, true, true, true, true, + true, true, true, true, true, true, true + ]); + }); }); diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 27e4621..a882578 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -176,11 +176,13 @@ export const useCalendar = ({ const daysWithEvents = days.map((day) => { const dayKey = day.toString() const dailyEvents = eventMap.get(dayKey) ?? [] + const isInCurrentPeriod = day.month === state.currPeriod.month return { date: day, events: dailyEvents, isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, + isInCurrentPeriod, } }) From d76ddc148110aab895a06004c25157ff5ce11889 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 00:14:51 +0200 Subject: [PATCH 031/128] feat: useCalendar hook --- docs/framework/react/reference/useCalendar.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index 6a1941f..a53e07f 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -119,7 +119,11 @@ const CalendarComponent = ({ events }) => { {weeks.map((week, weekIndex) => ( {week.map((day) => ( - +
{day.date.day}
From f6b6f29e9ae1bccc429d06312cdcce0d737e0aa4 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 01:48:24 +0200 Subject: [PATCH 032/128] test: useCalendar --- .../react-time/src/tests/useCalendar.test.tsx | 56 +++++++++++++++++++ .../react-time/src/useCalendar/useCalendar.ts | 8 +-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index e4926ee..ca273c1 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -248,4 +248,60 @@ describe('useCalendar', () => { true, true, true, true, true, true, true ]); }); + + test('should navigate to a specific period correctly', () => { + const { result } = renderHook(() => useCalendar({ events, viewMode: 'month', locale: 'en-US' })) + const specificDate = Temporal.PlainDate.from('2024-05-15') + + act(() => { + result.current.goToSpecificPeriod(specificDate) + }) + + expect(result.current.currPeriod).toEqual(specificDate) + }) + + test('should navigate to the previous period correctly', () => { + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + ) + + act(() => { + result.current.goToPreviousPeriod() + }) + + const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ + months: 1, + }) + + expect(result.current.currPeriod).toEqual(expectedPreviousMonth) + }) + + test('should navigate to the next period correctly', () => { + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + ) + + act(() => { + result.current.goToNextPeriod() + }) + + const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) + + expect(result.current.currPeriod).toEqual(expectedNextMonth) + }) + + test('should reset to the current period correctly', () => { + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + ) + + act(() => { + result.current.goToNextPeriod() + result.current.goToCurrentPeriod() + }) + + expect(result.current.currPeriod).toEqual( + Temporal.Now.plainDateISO(), + ) + }) }); diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index a882578..65c7e98 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -191,15 +191,15 @@ export const useCalendar = ({ ? [...getChunks(daysWithEvents, 7)] : [daysWithEvents] - const goToPreviousPeriod = useCallback>(() => { + const goToPreviousPeriod = useCallback(() => { dispatch(actions.goToPreviousPeriod()) }, [dispatch]) - const goToNextPeriod = useCallback>(() => { + const goToNextPeriod = useCallback(() => { dispatch(actions.goToNextPeriod()); }, [dispatch]) - const goToCurrentPeriod = useCallback>(() => { + const goToCurrentPeriod = useCallback(() => { dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO())) }, [dispatch]) @@ -337,13 +337,13 @@ export const useCalendar = ({ }, [locale, weekStartsOn]) return { + ...state, firstDayOfPeriod: state.viewMode === 'month' ? firstDayOfMonth : state.viewMode === 'week' ? firstDayOfWeek : state.currPeriod, - currPeriod: state.currPeriod.toString({ calendarName: 'auto' }), goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, From d9ed5e750e0270be3585aef7cf5bcef64e7ca77b Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 01:49:16 +0200 Subject: [PATCH 033/128] test: useCalendar --- .../react-time/src/tests/useCalendar.test.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index ca273c1..0933e15 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -77,21 +77,6 @@ describe('useCalendar', () => { expect(result.current.firstDayOfPeriod).toEqual(expectedFirstDayOfNextMonth) }) - test('should reset to the current period correctly', () => { - const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month' }), - ) - - act(() => { - result.current.goToNextPeriod(mockEvent) - result.current.goToCurrentPeriod(mockEvent) - }) - - expect(result.current.currPeriod).toBe( - Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }), - ) - }) - test('should change view mode correctly', () => { const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' }), From 7b8662b64b6531ed6ccf5ba1607f563474d87cb9 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 01:50:00 +0200 Subject: [PATCH 034/128] test: useCalendar --- packages/react-time/src/tests/useCalendar.test.tsx | 6 ++---- packages/react-time/src/useCalendar/useCalendar.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 0933e15..65a580b 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -23,8 +23,6 @@ describe('useCalendar', () => { }, ] - const mockEvent = {} as React.MouseEvent - test('should initialize with the correct view mode and current period', () => { const { result } = renderHook(() => useCalendar({ events, viewMode: 'month' }), @@ -41,7 +39,7 @@ describe('useCalendar', () => { ) act(() => { - result.current.goToPreviousPeriod(mockEvent) + result.current.goToPreviousPeriod() }) const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ @@ -64,7 +62,7 @@ describe('useCalendar', () => { ) act(() => { - result.current.goToNextPeriod(mockEvent) + result.current.goToNextPeriod() }) const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 65c7e98..9774676 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -3,7 +3,7 @@ import { Temporal } from '@js-temporal/polyfill' import { actions } from './calendarActions' import { useCalendarReducer } from './useCalendarReducer' import type { Event } from './useCalendarState' -import type { CSSProperties, MouseEventHandler } from 'react' +import type { CSSProperties } from 'react' export const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { const date = Temporal.PlainDate.from(currWeek) From be2e360b69d0ddca18c73afafd3af1ff72396553 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 13:34:17 +0200 Subject: [PATCH 035/128] feat: custom reducer --- .../react-time/src/tests/useCalendar.test.tsx | 27 +++++++++++++++++++ .../react-time/src/useCalendar/useCalendar.ts | 9 ++++--- .../src/useCalendar/useCalendarReducer.ts | 13 ++++----- .../src/useCalendar/useCalendarState.ts | 2 +- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 65a580b..ba71454 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -2,6 +2,9 @@ import { Temporal } from '@js-temporal/polyfill' import { afterEach, describe, expect, test, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' import { useCalendar } from '../useCalendar' +import { actions } from '../useCalendar/calendarActions'; +import type { UseCalendarAction} from '../useCalendar/calendarActions'; +import type { UseCalendarState } from '../useCalendar/useCalendarState'; describe('useCalendar', () => { afterEach(() => { @@ -287,4 +290,28 @@ describe('useCalendar', () => { Temporal.Now.plainDateISO(), ) }) + + test(`should allow overriding the reducer`, () => { + const customReducer = (state: UseCalendarState, action: UseCalendarAction) => { + if (action.type === actions.goToNextPeriod().type) { + return { + ...state, + currPeriod: state.currPeriod.add({ months: 2 }), + } + } + + return state + } + + const { result } = renderHook(() => + useCalendar({ events, viewMode: 'month', reducer: customReducer }) + ) + + act(() => { + result.current.goToNextPeriod() + }) + + const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 2 }) + expect(result.current.currPeriod).toEqual(expectedNextMonth) + }); }); diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 9774676..a24629a 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -2,7 +2,8 @@ import { useCallback, useEffect, useMemo } from 'react' import { Temporal } from '@js-temporal/polyfill' import { actions } from './calendarActions' import { useCalendarReducer } from './useCalendarReducer' -import type { Event } from './useCalendarState' +import type { UseCalendarAction} from './calendarActions'; +import type { Event, UseCalendarState } from './useCalendarState' import type { CSSProperties } from 'react' export const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { @@ -13,12 +14,13 @@ export const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { export const getFirstDayOfMonth = (currMonth: string) => Temporal.PlainDate.from(`${currMonth}-01`) -interface UseCalendarProps { +interface UseCalendarProps { weekStartsOn?: number events?: TEvent[] viewMode: 'month' | 'week' | number locale?: Parameters['0'] onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void + reducer?: (state: TState, action: TAction) => TState } const getChunks = function* (arr: T[], n: number) { @@ -92,6 +94,7 @@ export const useCalendar = ({ viewMode: initialViewMode, locale, onChangeViewMode, + reducer, }: UseCalendarProps) => { const today = Temporal.Now.plainDateISO() const [state, dispatch] = useCalendarReducer({ @@ -99,7 +102,7 @@ export const useCalendar = ({ viewMode: initialViewMode, currentTime: Temporal.Now.plainDateTimeISO(), weekStartsOn, - }) + }, reducer) const firstDayOfMonth = getFirstDayOfMonth( state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts index 5aa28d2..70a9954 100644 --- a/packages/react-time/src/useCalendar/useCalendarReducer.ts +++ b/packages/react-time/src/useCalendar/useCalendarReducer.ts @@ -1,12 +1,12 @@ -import { useReducer } from 'react' +import { useMemo, useReducer } from 'react' import { createReducer } from 'typesafe-actions' import { getFirstDayOfMonth, getFirstDayOfWeek } from '@tanstack/time' import { type UseCalendarAction, actions } from './calendarActions' -import type { CalendarState } from './useCalendarState' +import type { UseCalendarState } from './useCalendarState' -const createCalendarReducer = (initialState: CalendarState) => { - return createReducer(initialState) +const createCalendarReducer = (initialState: UseCalendarState) => { + return createReducer(initialState) .handleAction(actions.setCurrentPeriod, (state, action) => ({ ...state, currPeriod: action.payload, @@ -90,10 +90,11 @@ const createCalendarReducer = (initialState: CalendarState) => { } export const useCalendarReducer = < - TState extends CalendarState = CalendarState, + TState extends UseCalendarState = UseCalendarState, >( initialState: TState, + extReducer?: (state: TState, action: UseCalendarAction) => TState, ) => { - const reducer = createCalendarReducer(initialState) + const reducer = useMemo(() => extReducer ?? createCalendarReducer(initialState), [extReducer, initialState]) return useReducer(reducer, initialState) } diff --git a/packages/react-time/src/useCalendar/useCalendarState.ts b/packages/react-time/src/useCalendar/useCalendarState.ts index cd57a88..a2d36e4 100644 --- a/packages/react-time/src/useCalendar/useCalendarState.ts +++ b/packages/react-time/src/useCalendar/useCalendarState.ts @@ -7,7 +7,7 @@ export interface Event { title: string; } -export interface CalendarState { +export interface UseCalendarState { currPeriod: Temporal.PlainDate; viewMode: 'month' | 'week' | number; currentTime: Temporal.PlainDateTime; From 4ccd56bdf108ad2262a0d7f1c12b0b8575ff0228 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 13:37:37 +0200 Subject: [PATCH 036/128] docs: tsdocs --- .../react-time/src/useCalendar/useCalendar.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index a24629a..30ddfe5 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -15,11 +15,30 @@ export const getFirstDayOfMonth = (currMonth: string) => Temporal.PlainDate.from(`${currMonth}-01`) interface UseCalendarProps { + /** + * The day of the week the calendar should start on (1 for Monday, 7 for Sunday). + * @default 1 + */ weekStartsOn?: number + /** + * An array of events that the calendar should display. + */ events?: TEvent[] + /** + * The initial view mode of the calendar. It can be 'month', 'week', or a number representing the number of days in a custom view mode. + */ viewMode: 'month' | 'week' | number + /** + * The locale to use for formatting dates and times. + */ locale?: Parameters['0'] + /** + * Callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. + */ onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void + /** + * Custom reducer function to manage the state of the calendar. + */ reducer?: (state: TState, action: TAction) => TState } @@ -88,6 +107,31 @@ const generateDateRange = ( return dates } +/** + * Hook to manage the state and behavior of a calendar. + * + * @param {UseCalendarProps} props - The configuration properties for the calendar. + * @param {number} [props.weekStartsOn=1] - The day of the week the calendar should start on (1 for Monday, 7 for Sunday). + * @param {TEvent[]} [props.events] - An array of events that the calendar should display. + * @param {'month' | 'week' | number} props.viewMode - The initial view mode of the calendar. It can be 'month', 'week', or a number representing the number of days in a custom view mode. + * @param {Intl.LocalesArgument} [props.locale] - The locale to use for formatting dates and times. + * @param {Function} [props.onChangeViewMode] - Callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. + * @param {Function} [props.reducer] - Custom reducer function to manage the state of the calendar. + * + * @returns {Object} calendarState - The state and functions for managing the calendar. + * @returns {Temporal.PlainDate} calendarState.firstDayOfPeriod - The first day of the current period displayed by the calendar. + * @returns {Temporal.PlainDate} calendarState.currPeriod - The current period displayed by the calendar. + * @returns {Function} calendarState.goToPreviousPeriod - Function to navigate to the previous period. + * @returns {Function} calendarState.goToNextPeriod - Function to navigate to the next period. + * @returns {Function} calendarState.goToCurrentPeriod - Function to navigate to the current period. + * @returns {Function} calendarState.goToSpecificPeriod - Function to navigate to a specific period. + * @returns {Array>} calendarState.weeks - The calendar grid, where each cell contains the date and events for that day. + * @returns {string[]} calendarState.daysNames - An array of day names based on the locale and week start day. + * @returns {'month' | 'week' | number} calendarState.viewMode - The current view mode of the calendar. + * @returns {Function} calendarState.changeViewMode - Function to change the view mode of the calendar. + * @returns {Function} calendarState.getEventProps - Function to retrieve the style properties for a specific event based on its ID. + * @returns {Function} calendarState.currentTimeMarkerProps - Function to retrieve the style properties and current time for the current time marker. + */ export const useCalendar = ({ weekStartsOn = 1, events, From cc59409a7aed4a49060dc05dd8b729eed3d77205 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 13:37:45 +0200 Subject: [PATCH 037/128] docs: tsdocs --- docs/framework/react/reference/useCalendar.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index a53e07f..620e1fe 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -32,6 +32,8 @@ export function useCalendar({ - This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. - `onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void` - This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. +- `reducer?: (state: CalendarState, action: CalendarAction) => CalendarState` + - This parameter is an optional custom reducer function that can be used to manage the state of the calendar. #### Returns From 8d51b58e3e4a2dde91b817c07bd268a7602027f4 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 13:46:28 +0200 Subject: [PATCH 038/128] docs: tsdocs --- packages/react-time/src/useCalendar/useCalendar.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 30ddfe5..e12d456 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -397,7 +397,6 @@ export const useCalendar = ({ goToSpecificPeriod, weeks, daysNames, - viewMode: state.viewMode, changeViewMode, getEventProps, currentTimeMarkerProps, From 1e7168446066e3be01ff0d233cfe4d0df6a4e89e Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 13:59:02 +0200 Subject: [PATCH 039/128] refactor: useCalendar --- .../react-time/src/useCalendar/calendarActions.ts | 4 ++-- packages/react-time/src/useCalendar/useCalendar.ts | 13 ++++++------- .../src/useCalendar/useCalendarReducer.ts | 8 ++++---- .../react-time/src/useCalendar/useCalendarState.ts | 1 - 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/react-time/src/useCalendar/calendarActions.ts b/packages/react-time/src/useCalendar/calendarActions.ts index aee447f..3eb69d4 100644 --- a/packages/react-time/src/useCalendar/calendarActions.ts +++ b/packages/react-time/src/useCalendar/calendarActions.ts @@ -5,8 +5,8 @@ import type { ActionType } from 'typesafe-actions'; const setViewMode = createAction('SET_VIEW_MODE')<'month' | 'week' | number>(); const updateCurrentTime = createAction('UPDATE_CURRENT_TIME')(); const setCurrentPeriod = createAction('SET_CURRENT_PERIOD')(); -const goToNextPeriod = createAction('SET_NEXT_PERIOD')(); -const goToPreviousPeriod = createAction('SET_PREVIOUS_PERIOD')(); +const goToNextPeriod = createAction('SET_NEXT_PERIOD')<{ weekStartsOn: number }>(); +const goToPreviousPeriod = createAction('SET_PREVIOUS_PERIOD')<{ weekStartsOn: number }>(); export const actions = { setCurrentPeriod, setViewMode, updateCurrentTime, goToNextPeriod, goToPreviousPeriod }; export type UseCalendarAction = ActionType; diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index e12d456..e12c438 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -145,7 +145,6 @@ export const useCalendar = ({ currPeriod: today, viewMode: initialViewMode, currentTime: Temporal.Now.plainDateTimeISO(), - weekStartsOn, }, reducer) const firstDayOfMonth = getFirstDayOfMonth( @@ -154,7 +153,7 @@ export const useCalendar = ({ const firstDayOfWeek = getFirstDayOfWeek( state.currPeriod.toString(), - state.weekStartsOn, + weekStartsOn, ) const days = @@ -162,7 +161,7 @@ export const useCalendar = ({ ? Array.from( getChunks( generateDateRange( - firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - state.weekStartsOn + 7) % 7 }), + firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 }), firstDayOfMonth.add({ months: 1 }).subtract({ days: 1 }), ), 7, @@ -239,12 +238,12 @@ export const useCalendar = ({ : [daysWithEvents] const goToPreviousPeriod = useCallback(() => { - dispatch(actions.goToPreviousPeriod()) - }, [dispatch]) + dispatch(actions.goToPreviousPeriod({ weekStartsOn })) + }, [dispatch, weekStartsOn]) const goToNextPeriod = useCallback(() => { - dispatch(actions.goToNextPeriod()); - }, [dispatch]) + dispatch(actions.goToNextPeriod({ weekStartsOn })) + }, [dispatch, weekStartsOn]) const goToCurrentPeriod = useCallback(() => { dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO())) diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts index 70a9954..b8d635b 100644 --- a/packages/react-time/src/useCalendar/useCalendarReducer.ts +++ b/packages/react-time/src/useCalendar/useCalendarReducer.ts @@ -19,13 +19,13 @@ const createCalendarReducer = (initialState: UseCalendarState) => { ...state, currentTime: action.payload, })) - .handleAction(actions.goToPreviousPeriod, (state) => { + .handleAction(actions.goToPreviousPeriod, (state, action) => { const firstDayOfMonth = getFirstDayOfMonth( state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), ) const firstDayOfWeek = getFirstDayOfWeek( state.currPeriod.toString(), - state.weekStartsOn, + action.payload.weekStartsOn, ) switch (state.viewMode) { @@ -54,13 +54,13 @@ const createCalendarReducer = (initialState: UseCalendarState) => { } } }) - .handleAction(actions.goToNextPeriod, (state) => { + .handleAction(actions.goToNextPeriod, (state, action) => { const firstDayOfMonth = getFirstDayOfMonth( state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), ) const firstDayOfWeek = getFirstDayOfWeek( state.currPeriod.toString(), - state.weekStartsOn, + action.payload.weekStartsOn, ) switch (state.viewMode) { diff --git a/packages/react-time/src/useCalendar/useCalendarState.ts b/packages/react-time/src/useCalendar/useCalendarState.ts index a2d36e4..dd3679b 100644 --- a/packages/react-time/src/useCalendar/useCalendarState.ts +++ b/packages/react-time/src/useCalendar/useCalendarState.ts @@ -11,5 +11,4 @@ export interface UseCalendarState { currPeriod: Temporal.PlainDate; viewMode: 'month' | 'week' | number; currentTime: Temporal.PlainDateTime; - weekStartsOn: number; } From e134065f530bf5330a4ac797b0968f48532d6d95 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 16:40:27 +0200 Subject: [PATCH 040/128] refactor: useCalendar --- .../react-time/src/tests/useCalendar.test.tsx | 9 +- .../react-time/src/useCalendar/useCalendar.ts | 309 ++++++------------ .../src/useCalendar/useCalendarReducer.ts | 26 +- 3 files changed, 111 insertions(+), 233 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index ba71454..4f52462 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -2,7 +2,6 @@ import { Temporal } from '@js-temporal/polyfill' import { afterEach, describe, expect, test, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' import { useCalendar } from '../useCalendar' -import { actions } from '../useCalendar/calendarActions'; import type { UseCalendarAction} from '../useCalendar/calendarActions'; import type { UseCalendarState } from '../useCalendar/useCalendarState'; @@ -31,8 +30,8 @@ describe('useCalendar', () => { useCalendar({ events, viewMode: 'month' }), ) expect(result.current.viewMode).toBe('month') - expect(result.current.currPeriod).toBe( - Temporal.Now.plainDateISO().toString({ calendarName: 'auto' }), + expect(result.current.currPeriod.toString()).toBe( + Temporal.Now.plainDateISO().toString(), ) }) @@ -99,7 +98,7 @@ describe('useCalendar', () => { result.current.goToSpecificPeriod(Temporal.PlainDate.from('2024-06-01')) }) - expect(result.current.currPeriod).toBe('2024-06-01') + expect(result.current.currPeriod.toString()).toBe('2024-06-01') }) test('should return the correct props for an event', () => { @@ -293,7 +292,7 @@ describe('useCalendar', () => { test(`should allow overriding the reducer`, () => { const customReducer = (state: UseCalendarState, action: UseCalendarAction) => { - if (action.type === actions.goToNextPeriod().type) { + if (action.type === 'SET_NEXT_PERIOD') { return { ...state, currPeriod: state.currPeriod.add({ months: 2 }), diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index e12c438..d91929f 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo } from 'react' +import { useCallback, useEffect, useMemo, useTransition } from 'react' import { Temporal } from '@js-temporal/polyfill' import { actions } from './calendarActions' import { useCalendarReducer } from './useCalendarReducer' @@ -54,39 +54,14 @@ const splitMultiDayEvents = (event: TEvent): TEvent[] => { const events: TEvent[] = [] let currentDay = startDate - while ( - Temporal.PlainDate.compare( - currentDay.toPlainDate(), - endDate.toPlainDate(), - ) < 0 - ) { - const startOfCurrentDay = currentDay.with({ - hour: 0, - minute: 0, - second: 0, - millisecond: 0, - }) - const endOfCurrentDay = currentDay.with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }) + while (Temporal.PlainDate.compare(currentDay.toPlainDate(), endDate.toPlainDate()) < 0) { + const startOfCurrentDay = currentDay.with({ hour: 0, minute: 0, second: 0, millisecond: 0 }) + const endOfCurrentDay = currentDay.with({ hour: 23, minute: 59, second: 59, millisecond: 999 }) - const eventStart = - Temporal.PlainDateTime.compare(currentDay, startDate) === 0 - ? startDate - : startOfCurrentDay - const eventEnd = - Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 - ? endDate - : endOfCurrentDay - - events.push({ - ...event, - startDate: eventStart, - endDate: eventEnd, - }) + const eventStart = Temporal.PlainDateTime.compare(currentDay, startDate) === 0 ? startDate : startOfCurrentDay + const eventEnd = Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 ? endDate : endOfCurrentDay + + events.push({ ...event, startDate: eventStart, endDate: eventEnd }) currentDay = startOfCurrentDay.add({ days: 1 }) } @@ -94,10 +69,7 @@ const splitMultiDayEvents = (event: TEvent): TEvent[] => { return events } -const generateDateRange = ( - start: Temporal.PlainDate, - end: Temporal.PlainDate, -) => { +const generateDateRange = (start: Temporal.PlainDate, end: Temporal.PlainDate) => { const dates: Temporal.PlainDate[] = [] let current = start while (Temporal.PlainDate.compare(current, end) <= 0) { @@ -146,59 +118,23 @@ export const useCalendar = ({ viewMode: initialViewMode, currentTime: Temporal.Now.plainDateTimeISO(), }, reducer) - - const firstDayOfMonth = getFirstDayOfMonth( - state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), - ) - - const firstDayOfWeek = getFirstDayOfWeek( - state.currPeriod.toString(), - weekStartsOn, - ) - - const days = - state.viewMode === 'month' - ? Array.from( - getChunks( - generateDateRange( - firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 }), - firstDayOfMonth.add({ months: 1 }).subtract({ days: 1 }), - ), - 7, - ), - ).flat() - : state.viewMode === 'week' - ? Array.from( - getChunks( - generateDateRange( - firstDayOfWeek, - firstDayOfWeek.add({ days: 6 }), - ), - 7, - ), - ).flat() - : Array.from( - getChunks( - generateDateRange( - state.currPeriod, - state.currPeriod.add({ days: state.viewMode - 1 }), - ), - state.viewMode, - ), - ).flat() + + const firstDayOfMonth = useMemo(() => getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)), [state.currPeriod]) + + const firstDayOfWeek = useMemo(() => getFirstDayOfWeek(state.currPeriod.toString(), weekStartsOn), [state.currPeriod, weekStartsOn]) + + const days = useMemo(() => { + const start = state.viewMode === 'month' ? firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 }) : firstDayOfWeek + const end = state.viewMode === 'month' ? firstDayOfMonth.add({ months: 1 }).subtract({ days: 1 }) : firstDayOfWeek.add({ days: 6 }) + return Array.from(getChunks(generateDateRange(start, end), 7)).flat() + }, [state.viewMode, firstDayOfMonth, firstDayOfWeek, weekStartsOn]) const eventMap = useMemo(() => { const map = new Map() - events?.forEach((event) => { const eventStartDate = Temporal.PlainDateTime.from(event.startDate) const eventEndDate = Temporal.PlainDateTime.from(event.endDate) - if ( - Temporal.PlainDate.compare( - eventStartDate.toPlainDate(), - eventEndDate.toPlainDate(), - ) !== 0 - ) { + if (Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0) { const splitEvents = splitMultiDayEvents(event) splitEvents.forEach((splitEvent) => { const splitKey = splitEvent.startDate.toString().split('T')[0] @@ -215,147 +151,108 @@ export const useCalendar = ({ } } }) - return map }, [events]) - const daysWithEvents = days.map((day) => { + const daysWithEvents = useMemo(() => days.map((day) => { const dayKey = day.toString() const dailyEvents = eventMap.get(dayKey) ?? [] const isInCurrentPeriod = day.month === state.currPeriod.month + return { date: day, events: dailyEvents, isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, isInCurrentPeriod } + }), [days, eventMap, state.currPeriod]) - return { - date: day, - events: dailyEvents, - isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, - isInCurrentPeriod, - } - }) + const weeks = useMemo(() => state.viewMode === 'month' ? [...getChunks(daysWithEvents, 7)] : [daysWithEvents], [state.viewMode, daysWithEvents]) - const weeks = - state.viewMode === 'month' - ? [...getChunks(daysWithEvents, 7)] - : [daysWithEvents] + const [isPending, startTransition] = useTransition() - const goToPreviousPeriod = useCallback(() => { - dispatch(actions.goToPreviousPeriod({ weekStartsOn })) - }, [dispatch, weekStartsOn]) + const goToPreviousPeriod = useCallback(() => startTransition(() => dispatch(actions.goToPreviousPeriod({ weekStartsOn }))), [dispatch, weekStartsOn]) - const goToNextPeriod = useCallback(() => { - dispatch(actions.goToNextPeriod({ weekStartsOn })) - }, [dispatch, weekStartsOn]) + const goToNextPeriod = useCallback(() => startTransition(() => dispatch(actions.goToNextPeriod({ weekStartsOn }))), [dispatch, weekStartsOn]) - const goToCurrentPeriod = useCallback(() => { - dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO())) - }, [dispatch]) + const goToCurrentPeriod = useCallback(() => startTransition(() => dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO()))), [dispatch]) - const goToSpecificPeriod = useCallback( - (date: Temporal.PlainDate) => { - dispatch(actions.setCurrentPeriod(date)) - }, - [dispatch], - ) + const goToSpecificPeriod = useCallback((date: Temporal.PlainDate) => startTransition(() => dispatch(actions.setCurrentPeriod(date))), [dispatch]) - const changeViewMode = useCallback( - (newViewMode: 'month' | 'week' | number) => { + const changeViewMode = useCallback((newViewMode: 'month' | 'week' | number) => { + startTransition(() => { dispatch(actions.setViewMode(newViewMode)) onChangeViewMode?.(newViewMode) - }, - [dispatch, onChangeViewMode], - ) + }) + }, [dispatch, onChangeViewMode]) - const getEventProps = useCallback( - (id: Event['id']): { style: CSSProperties } | null => { - const event = [...eventMap.values()] - .flat() - .find((currEvent) => currEvent.id === id) - if (!event) return null + const getEventProps = useCallback((id: Event['id']): { style: CSSProperties } | null => { + const event = [...eventMap.values()].flat().find((currEvent) => currEvent.id === id) + if (!event) return null - const eventStartDate = Temporal.PlainDateTime.from(event.startDate) - const eventEndDate = Temporal.PlainDateTime.from(event.endDate) + const eventStartDate = Temporal.PlainDateTime.from(event.startDate) + const eventEndDate = Temporal.PlainDateTime.from(event.endDate) + const isSplitEvent = Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0 - const isSplitEvent = - Temporal.PlainDate.compare( - eventStartDate.toPlainDate(), - eventEndDate.toPlainDate(), - ) !== 0 - - let percentageOfDay - let eventHeightInMinutes - - if (isSplitEvent) { - const isStartPart = - eventStartDate.hour !== 0 || eventStartDate.minute !== 0 - if (isStartPart) { - const eventTimeInMinutes = - eventStartDate.hour * 60 + eventStartDate.minute - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 - eventHeightInMinutes = 24 * 60 - eventTimeInMinutes - } else { - percentageOfDay = 0 - eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute - } - } else { - const eventTimeInMinutes = - eventStartDate.hour * 60 + eventStartDate.minute + let percentageOfDay + let eventHeightInMinutes + + if (isSplitEvent) { + const isStartPart = eventStartDate.hour !== 0 || eventStartDate.minute !== 0 + if (isStartPart) { + const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 - const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute - eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes + eventHeightInMinutes = 24 * 60 - eventTimeInMinutes + } else { + percentageOfDay = 0 + eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute } + } else { + const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 + const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute + eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes + } - const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20) - - const overlappingEvents = [...eventMap.values()].flat().filter((e) => { - const eStartDate = Temporal.PlainDateTime.from(e.startDate) - const eEndDate = Temporal.PlainDateTime.from(e.endDate) - return ( - (e.id !== id && - Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && - Temporal.PlainDateTime.compare(eventStartDate, eEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eventEndDate, eStartDate) >= 0 && - Temporal.PlainDateTime.compare(eventEndDate, eEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eStartDate, eventStartDate) >= 0 && - Temporal.PlainDateTime.compare(eStartDate, eventEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eEndDate, eventStartDate) >= 0 && - Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) - ) - }) - - const eventIndex = overlappingEvents.findIndex((e) => e.id === id) - const totalOverlaps = overlappingEvents.length - const sidePadding = 2 - const innerPadding = 2 - const totalInnerPadding = (totalOverlaps - 1) * innerPadding - const availableWidth = 100 - totalInnerPadding - 2 * sidePadding - const eventWidth = - totalOverlaps > 0 - ? availableWidth / totalOverlaps - : 100 - 2 * sidePadding - const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) - - if (state.viewMode === 'week' || typeof state.viewMode === 'number') { - return { - style: { - position: 'absolute', - top: `min(${percentageOfDay}%, calc(100% - 55px))`, - left: `${eventLeft}%`, - width: `${eventWidth}%`, - margin: 0, - height: `${eventHeight}%`, - }, - } + const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20) + + const overlappingEvents = [...eventMap.values()].flat().filter((e) => { + const eStartDate = Temporal.PlainDateTime.from(e.startDate) + const eEndDate = Temporal.PlainDateTime.from(e.endDate) + return ( + (e.id !== id && + Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && + Temporal.PlainDateTime.compare(eventStartDate, eEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eventEndDate, eStartDate) >= 0 && + Temporal.PlainDateTime.compare(eventEndDate, eEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eStartDate, eventStartDate) >= 0 && + Temporal.PlainDateTime.compare(eStartDate, eventEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eEndDate, eventStartDate) >= 0 && + Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) + ) + }) + + const eventIndex = overlappingEvents.findIndex((e) => e.id === id) + const totalOverlaps = overlappingEvents.length + const sidePadding = 2 + const innerPadding = 2 + const totalInnerPadding = (totalOverlaps - 1) * innerPadding + const availableWidth = 100 - totalInnerPadding - 2 * sidePadding + const eventWidth = totalOverlaps > 0 ? availableWidth / totalOverlaps : 100 - 2 * sidePadding + const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) + + if (state.viewMode === 'week' || typeof state.viewMode === 'number') { + return { + style: { + position: 'absolute', + top: `min(${percentageOfDay}%, calc(100% - 55px))`, + left: `${eventLeft}%`, + width: `${eventWidth}%`, + margin: 0, + height: `${eventHeight}%`, + }, } + } - return null - }, - [eventMap, state.viewMode], - ) + return null + }, [eventMap, state.viewMode]) useEffect(() => { - const intervalId = setInterval(() => { - dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())) - }, 60000) - + const intervalId = setInterval(() => dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())), 60000) return () => clearInterval(intervalId) }, [dispatch]) @@ -376,7 +273,7 @@ export const useCalendar = ({ const daysNames = useMemo(() => { const baseDate = Temporal.PlainDate.from('2024-01-01') - return Array.from({ length: 7 }).map((_, i) => + return Array.from({ length: 7 }).map((_, i) => baseDate.add({ days: (i + weekStartsOn - 1) % 7 }) .toLocaleString(locale, { weekday: 'short' }) ) @@ -384,12 +281,7 @@ export const useCalendar = ({ return { ...state, - firstDayOfPeriod: - state.viewMode === 'month' - ? firstDayOfMonth - : state.viewMode === 'week' - ? firstDayOfWeek - : state.currPeriod, + firstDayOfPeriod: state.viewMode === 'month' ? firstDayOfMonth : state.viewMode === 'week' ? firstDayOfWeek : state.currPeriod, goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, @@ -399,5 +291,6 @@ export const useCalendar = ({ changeViewMode, getEventProps, currentTimeMarkerProps, + isPending } -} +} \ No newline at end of file diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts index b8d635b..0789b6c 100644 --- a/packages/react-time/src/useCalendar/useCalendarReducer.ts +++ b/packages/react-time/src/useCalendar/useCalendarReducer.ts @@ -20,13 +20,8 @@ const createCalendarReducer = (initialState: UseCalendarState) => { currentTime: action.payload, })) .handleAction(actions.goToPreviousPeriod, (state, action) => { - const firstDayOfMonth = getFirstDayOfMonth( - state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), - ) - const firstDayOfWeek = getFirstDayOfWeek( - state.currPeriod.toString(), - action.payload.weekStartsOn, - ) + const firstDayOfMonth = getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)) + const firstDayOfWeek = getFirstDayOfWeek(state.currPeriod.toString(), action.payload.weekStartsOn) switch (state.viewMode) { case 'month': { @@ -44,9 +39,7 @@ const createCalendarReducer = (initialState: UseCalendarState) => { } } default: { - const prevCustomStart = state.currPeriod.subtract({ - days: state.viewMode, - }) + const prevCustomStart = state.currPeriod.subtract({ days: state.viewMode }) return { ...state, currPeriod: prevCustomStart, @@ -55,13 +48,8 @@ const createCalendarReducer = (initialState: UseCalendarState) => { } }) .handleAction(actions.goToNextPeriod, (state, action) => { - const firstDayOfMonth = getFirstDayOfMonth( - state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), - ) - const firstDayOfWeek = getFirstDayOfWeek( - state.currPeriod.toString(), - action.payload.weekStartsOn, - ) + const firstDayOfMonth = getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)) + const firstDayOfWeek = getFirstDayOfWeek(state.currPeriod.toString(), action.payload.weekStartsOn) switch (state.viewMode) { case 'month': { @@ -89,9 +77,7 @@ const createCalendarReducer = (initialState: UseCalendarState) => { }) } -export const useCalendarReducer = < - TState extends UseCalendarState = UseCalendarState, ->( +export const useCalendarReducer = ( initialState: TState, extReducer?: (state: TState, action: UseCalendarAction) => TState, ) => { From f3f47ed1999b8671c69fb552d10f260c476226f4 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 6 Jun 2024 16:40:34 +0200 Subject: [PATCH 041/128] refactor: useCalendar --- docs/framework/react/reference/useCalendar.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index 620e1fe..5454204 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -66,6 +66,8 @@ export function useCalendar({ - This function is used to retrieve the style properties for a specific event based on its ID. - `currentTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` - This function is used to retrieve the style properties and current time for the current time marker. +- `isPending: boolean` + - This value represents whether the calendar is in a pending state. #### Example Usage From 1ee814a0e68ae2ef2cddd07dd051a01e4bb222e6 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 9 Jun 2024 18:08:14 +0200 Subject: [PATCH 042/128] refactor: change viewMode type --- .../react-time/src/tests/useCalendar.test.tsx | 40 ++++++------- .../src/useCalendar/calendarActions.ts | 3 +- .../react-time/src/useCalendar/useCalendar.ts | 37 ++++++------ .../src/useCalendar/useCalendarReducer.ts | 56 ++++++++++--------- .../src/useCalendar/useCalendarState.ts | 19 ++++--- 5 files changed, 82 insertions(+), 73 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 4f52462..bfecedc 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -27,9 +27,9 @@ describe('useCalendar', () => { test('should initialize with the correct view mode and current period', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month' }), + useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), ) - expect(result.current.viewMode).toBe('month') + expect(result.current.viewMode).toEqual({ value: 1, unit: 'months' }) expect(result.current.currPeriod.toString()).toBe( Temporal.Now.plainDateISO().toString(), ) @@ -37,7 +37,7 @@ describe('useCalendar', () => { test('should navigate to the previous period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month' }), + useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), ) act(() => { @@ -60,7 +60,7 @@ describe('useCalendar', () => { test('should navigate to the next period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month' }), + useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), ) act(() => { @@ -79,19 +79,19 @@ describe('useCalendar', () => { test('should change view mode correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month' }), + useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), ) act(() => { - result.current.changeViewMode('week') + result.current.changeViewMode({ value: 1, unit: 'weeks' }) }) - expect(result.current.viewMode).toBe('week') + expect(result.current.viewMode).toEqual({ value: 1, unit: 'weeks' }) }) test('should select a day correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month' }), + useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), ) act(() => { @@ -103,7 +103,7 @@ describe('useCalendar', () => { test('should return the correct props for an event', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'week' }), + useCalendar({ events, viewMode: { value: 1, unit: 'weeks' } }), ) const eventProps = result.current.getEventProps('1') @@ -136,7 +136,7 @@ describe('useCalendar', () => { }, ] const { result } = renderHook(() => - useCalendar({ events: overlappingEvents, viewMode: 'week' }), + useCalendar({ events: overlappingEvents, viewMode: { value: 1, unit: 'weeks' } }), ) const event1Props = result.current.getEventProps('1') @@ -169,7 +169,7 @@ describe('useCalendar', () => { vi.setSystemTime(new Date('2024-06-01T11:00:00')); const { result } = renderHook(() => - useCalendar({ events, viewMode: 'week' }), + useCalendar({ events, viewMode: { value: 1, unit: 'weeks' } }), ) const currentTimeMarkerProps = result.current.currentTimeMarkerProps() @@ -188,7 +188,7 @@ describe('useCalendar', () => { vi.setSystemTime(new Date('2024-06-01T11:00:00')); const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }), ); const { weeks } = result.current; @@ -202,14 +202,14 @@ describe('useCalendar', () => { test('should return the correct day names based on weekStartsOn', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month', locale: 'en-US', weekStartsOn: 1 }) + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US', weekStartsOn: 1 }) ); const { daysNames } = result.current; expect(daysNames).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']); const { result: resultSundayStart } = renderHook(() => - useCalendar({ events, viewMode: 'month', locale: 'en-US', weekStartsOn: 7 }) + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US', weekStartsOn: 7 }) ); const { daysNames: sundayDaysNames } = resultSundayStart.current; @@ -219,7 +219,7 @@ describe('useCalendar', () => { test('should correctly mark days as in current period', () => { vi.setSystemTime(new Date('2024-06-01T11:00:00')); const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) ); const { weeks } = result.current; @@ -235,7 +235,7 @@ describe('useCalendar', () => { }); test('should navigate to a specific period correctly', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: 'month', locale: 'en-US' })) + const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'months' } })) const specificDate = Temporal.PlainDate.from('2024-05-15') act(() => { @@ -247,7 +247,7 @@ describe('useCalendar', () => { test('should navigate to the previous period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) ) act(() => { @@ -263,7 +263,7 @@ describe('useCalendar', () => { test('should navigate to the next period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) ) act(() => { @@ -277,7 +277,7 @@ describe('useCalendar', () => { test('should reset to the current period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month', locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) ) act(() => { @@ -303,7 +303,7 @@ describe('useCalendar', () => { } const { result } = renderHook(() => - useCalendar({ events, viewMode: 'month', reducer: customReducer }) + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, reducer: customReducer }), ) act(() => { diff --git a/packages/react-time/src/useCalendar/calendarActions.ts b/packages/react-time/src/useCalendar/calendarActions.ts index 3eb69d4..abf5c73 100644 --- a/packages/react-time/src/useCalendar/calendarActions.ts +++ b/packages/react-time/src/useCalendar/calendarActions.ts @@ -1,8 +1,9 @@ import { createAction } from 'typesafe-actions'; import type { Temporal } from '@js-temporal/polyfill'; import type { ActionType } from 'typesafe-actions'; +import type { UseCalendarState } from './useCalendarState'; -const setViewMode = createAction('SET_VIEW_MODE')<'month' | 'week' | number>(); +const setViewMode = createAction('SET_VIEW_MODE')(); const updateCurrentTime = createAction('UPDATE_CURRENT_TIME')(); const setCurrentPeriod = createAction('SET_CURRENT_PERIOD')(); const goToNextPeriod = createAction('SET_NEXT_PERIOD')<{ weekStartsOn: number }>(); diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index d91929f..9aaef02 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -27,7 +27,7 @@ interface UseCalendarProps void + onChangeViewMode?: (viewMode: UseCalendarState['viewMode']) => void /** * Custom reducer function to manage the state of the calendar. */ @@ -113,6 +113,7 @@ export const useCalendar = ({ reducer, }: UseCalendarProps) => { const today = Temporal.Now.plainDateISO() + const [isPending, startTransition] = useTransition() const [state, dispatch] = useCalendarReducer({ currPeriod: today, viewMode: initialViewMode, @@ -124,10 +125,10 @@ export const useCalendar = ({ const firstDayOfWeek = useMemo(() => getFirstDayOfWeek(state.currPeriod.toString(), weekStartsOn), [state.currPeriod, weekStartsOn]) const days = useMemo(() => { - const start = state.viewMode === 'month' ? firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 }) : firstDayOfWeek - const end = state.viewMode === 'month' ? firstDayOfMonth.add({ months: 1 }).subtract({ days: 1 }) : firstDayOfWeek.add({ days: 6 }) - return Array.from(getChunks(generateDateRange(start, end), 7)).flat() - }, [state.viewMode, firstDayOfMonth, firstDayOfWeek, weekStartsOn]) + const start = state.viewMode.unit === 'months' ? firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 }) : firstDayOfWeek; + const end = state.viewMode.unit === 'months' ? firstDayOfMonth.add({ months: state.viewMode.value }).subtract({ days: 1 }) : firstDayOfWeek.add({ days: 6 }); + return Array.from(getChunks(generateDateRange(start, end), 7)).flat(); + }, [state.viewMode, firstDayOfMonth, firstDayOfWeek, weekStartsOn]); const eventMap = useMemo(() => { const map = new Map() @@ -161,9 +162,7 @@ export const useCalendar = ({ return { date: day, events: dailyEvents, isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, isInCurrentPeriod } }), [days, eventMap, state.currPeriod]) - const weeks = useMemo(() => state.viewMode === 'month' ? [...getChunks(daysWithEvents, 7)] : [daysWithEvents], [state.viewMode, daysWithEvents]) - - const [isPending, startTransition] = useTransition() + const weeks = useMemo(() => state.viewMode.unit === 'months' ? [...getChunks(daysWithEvents, 7)] : [daysWithEvents], [state.viewMode, daysWithEvents]) const goToPreviousPeriod = useCallback(() => startTransition(() => dispatch(actions.goToPreviousPeriod({ weekStartsOn }))), [dispatch, weekStartsOn]) @@ -173,12 +172,12 @@ export const useCalendar = ({ const goToSpecificPeriod = useCallback((date: Temporal.PlainDate) => startTransition(() => dispatch(actions.setCurrentPeriod(date))), [dispatch]) - const changeViewMode = useCallback((newViewMode: 'month' | 'week' | number) => { + const changeViewMode = useCallback((newViewMode: UseCalendarState['viewMode']) => { startTransition(() => { - dispatch(actions.setViewMode(newViewMode)) - onChangeViewMode?.(newViewMode) - }) - }, [dispatch, onChangeViewMode]) + dispatch(actions.setViewMode(newViewMode)); + onChangeViewMode?.(newViewMode); + }); + }, [dispatch, onChangeViewMode]); const getEventProps = useCallback((id: Event['id']): { style: CSSProperties } | null => { const event = [...eventMap.values()].flat().find((currEvent) => currEvent.id === id) @@ -235,7 +234,9 @@ export const useCalendar = ({ const eventWidth = totalOverlaps > 0 ? availableWidth / totalOverlaps : 100 - 2 * sidePadding const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) - if (state.viewMode === 'week' || typeof state.viewMode === 'number') { + console.log('state.viewMode.unit', state.viewMode.unit) + + if (state.viewMode.unit === 'weeks' || state.viewMode.unit === 'days') { return { style: { position: 'absolute', @@ -247,8 +248,8 @@ export const useCalendar = ({ }, } } - - return null + + return null }, [eventMap, state.viewMode]) useEffect(() => { @@ -281,7 +282,7 @@ export const useCalendar = ({ return { ...state, - firstDayOfPeriod: state.viewMode === 'month' ? firstDayOfMonth : state.viewMode === 'week' ? firstDayOfWeek : state.currPeriod, + firstDayOfPeriod: state.viewMode.unit === 'months' ? firstDayOfMonth : state.viewMode.unit === 'weeks' ? firstDayOfWeek : state.currPeriod, goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts index 0789b6c..793c101 100644 --- a/packages/react-time/src/useCalendar/useCalendarReducer.ts +++ b/packages/react-time/src/useCalendar/useCalendarReducer.ts @@ -20,62 +20,66 @@ const createCalendarReducer = (initialState: UseCalendarState) => { currentTime: action.payload, })) .handleAction(actions.goToPreviousPeriod, (state, action) => { - const firstDayOfMonth = getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)) - const firstDayOfWeek = getFirstDayOfWeek(state.currPeriod.toString(), action.payload.weekStartsOn) + const firstDayOfMonth = getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)); + const firstDayOfWeek = getFirstDayOfWeek(state.currPeriod.toString(), action.payload.weekStartsOn); - switch (state.viewMode) { - case 'month': { - const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: 1 }) + switch (state.viewMode.unit) { + case 'months': { + const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: state.viewMode.value }); return { ...state, currPeriod: firstDayOfPrevMonth, - } + }; } - case 'week': { - const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: 1 }) + case 'weeks': { + const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: state.viewMode.value }); return { ...state, currPeriod: firstDayOfPrevWeek, - } + }; } - default: { - const prevCustomStart = state.currPeriod.subtract({ days: state.viewMode }) + case 'days': { + const prevCustomStart = state.currPeriod.subtract({ days: state.viewMode.value }); return { ...state, currPeriod: prevCustomStart, - } + }; } + default: + return state; } }) .handleAction(actions.goToNextPeriod, (state, action) => { - const firstDayOfMonth = getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)) - const firstDayOfWeek = getFirstDayOfWeek(state.currPeriod.toString(), action.payload.weekStartsOn) + const firstDayOfMonth = getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)); + const firstDayOfWeek = getFirstDayOfWeek(state.currPeriod.toString(), action.payload.weekStartsOn); - switch (state.viewMode) { - case 'month': { - const firstDayOfNextMonth = firstDayOfMonth.add({ months: 1 }) + switch (state.viewMode.unit) { + case 'months': { + const firstDayOfNextMonth = firstDayOfMonth.add({ months: state.viewMode.value }); return { ...state, currPeriod: firstDayOfNextMonth, - } + }; } - case 'week': { - const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: 1 }) + case 'weeks': { + const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: state.viewMode.value }); return { ...state, currPeriod: firstDayOfNextWeek, - } + }; } - default: { - const nextCustomStart = state.currPeriod.add({ days: state.viewMode }) + case 'days': { + const nextCustomStart = state.currPeriod.add({ days: state.viewMode.value }); return { ...state, currPeriod: nextCustomStart, - } + }; } + default: + return state; } - }) -} + }); +}; export const useCalendarReducer = ( initialState: TState, diff --git a/packages/react-time/src/useCalendar/useCalendarState.ts b/packages/react-time/src/useCalendar/useCalendarState.ts index dd3679b..b5a48fb 100644 --- a/packages/react-time/src/useCalendar/useCalendarState.ts +++ b/packages/react-time/src/useCalendar/useCalendarState.ts @@ -1,14 +1,17 @@ -import type { Temporal } from '@js-temporal/polyfill'; +import type { Temporal } from '@js-temporal/polyfill' export interface Event { - id: string; - startDate: Temporal.PlainDateTime; - endDate: Temporal.PlainDateTime; - title: string; + id: string + startDate: Temporal.PlainDateTime + endDate: Temporal.PlainDateTime + title: string } export interface UseCalendarState { - currPeriod: Temporal.PlainDate; - viewMode: 'month' | 'week' | number; - currentTime: Temporal.PlainDateTime; + currPeriod: Temporal.PlainDate + viewMode: { + value: number + unit: 'months' | 'weeks' | 'days' + } + currentTime: Temporal.PlainDateTime } From c2419defb9c6aa6c5794d77a0fd0cecbc1eef00d Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 9 Jun 2024 18:08:58 +0200 Subject: [PATCH 043/128] refactor: change viewMode type --- packages/react-time/src/useCalendar/useCalendar.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 9aaef02..478cfc0 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -234,8 +234,6 @@ export const useCalendar = ({ const eventWidth = totalOverlaps > 0 ? availableWidth / totalOverlaps : 100 - 2 * sidePadding const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) - console.log('state.viewMode.unit', state.viewMode.unit) - if (state.viewMode.unit === 'weeks' || state.viewMode.unit === 'days') { return { style: { From 215f7227b6ae8434500cbdb3d93a7ea0ffb0f593 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 9 Jun 2024 18:44:10 +0200 Subject: [PATCH 044/128] fix: missing days in view --- .../react-time/src/useCalendar/useCalendar.ts | 345 ++++++++++++------ 1 file changed, 230 insertions(+), 115 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 478cfc0..06baefa 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,10 +1,11 @@ +import type { CSSProperties } from 'react' import { useCallback, useEffect, useMemo, useTransition } from 'react' import { Temporal } from '@js-temporal/polyfill' + +import type { UseCalendarAction } from './calendarActions' +import type { Event, UseCalendarState } from './useCalendarState' import { actions } from './calendarActions' import { useCalendarReducer } from './useCalendarReducer' -import type { UseCalendarAction} from './calendarActions'; -import type { Event, UseCalendarState } from './useCalendarState' -import type { CSSProperties } from 'react' export const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { const date = Temporal.PlainDate.from(currWeek) @@ -14,7 +15,10 @@ export const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { export const getFirstDayOfMonth = (currMonth: string) => Temporal.PlainDate.from(`${currMonth}-01`) -interface UseCalendarProps { +interface UseCalendarProps< + TEvent extends Event, + TState extends UseCalendarState = UseCalendarState, +> { /** * The day of the week the calendar should start on (1 for Monday, 7 for Sunday). * @default 1 @@ -39,7 +43,10 @@ interface UseCalendarProps(state: TState, action: TAction) => TState + reducer?: ( + state: TState, + action: TAction, + ) => TState } const getChunks = function* (arr: T[], n: number) { @@ -54,12 +61,33 @@ const splitMultiDayEvents = (event: TEvent): TEvent[] => { const events: TEvent[] = [] let currentDay = startDate - while (Temporal.PlainDate.compare(currentDay.toPlainDate(), endDate.toPlainDate()) < 0) { - const startOfCurrentDay = currentDay.with({ hour: 0, minute: 0, second: 0, millisecond: 0 }) - const endOfCurrentDay = currentDay.with({ hour: 23, minute: 59, second: 59, millisecond: 999 }) + while ( + Temporal.PlainDate.compare( + currentDay.toPlainDate(), + endDate.toPlainDate(), + ) < 0 + ) { + const startOfCurrentDay = currentDay.with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }) + const endOfCurrentDay = currentDay.with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }) - const eventStart = Temporal.PlainDateTime.compare(currentDay, startDate) === 0 ? startDate : startOfCurrentDay - const eventEnd = Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 ? endDate : endOfCurrentDay + const eventStart = + Temporal.PlainDateTime.compare(currentDay, startDate) === 0 + ? startDate + : startOfCurrentDay + const eventEnd = + Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 + ? endDate + : endOfCurrentDay events.push({ ...event, startDate: eventStart, endDate: eventEnd }) @@ -69,7 +97,10 @@ const splitMultiDayEvents = (event: TEvent): TEvent[] => { return events } -const generateDateRange = (start: Temporal.PlainDate, end: Temporal.PlainDate) => { +const generateDateRange = ( + start: Temporal.PlainDate, + end: Temporal.PlainDate, +) => { const dates: Temporal.PlainDate[] = [] let current = start while (Temporal.PlainDate.compare(current, end) <= 0) { @@ -114,28 +145,62 @@ export const useCalendar = ({ }: UseCalendarProps) => { const today = Temporal.Now.plainDateISO() const [isPending, startTransition] = useTransition() - const [state, dispatch] = useCalendarReducer({ - currPeriod: today, - viewMode: initialViewMode, - currentTime: Temporal.Now.plainDateTimeISO(), - }, reducer) - - const firstDayOfMonth = useMemo(() => getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)), [state.currPeriod]) - - const firstDayOfWeek = useMemo(() => getFirstDayOfWeek(state.currPeriod.toString(), weekStartsOn), [state.currPeriod, weekStartsOn]) + const [state, dispatch] = useCalendarReducer( + { + currPeriod: today, + viewMode: initialViewMode, + currentTime: Temporal.Now.plainDateTimeISO(), + }, + reducer, + ) + + const firstDayOfMonth = useMemo( + () => + getFirstDayOfMonth( + state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), + ), + [state.currPeriod], + ) + + const firstDayOfWeek = useMemo( + () => getFirstDayOfWeek(state.currPeriod.toString(), weekStartsOn), + [state.currPeriod, weekStartsOn], + ) const days = useMemo(() => { - const start = state.viewMode.unit === 'months' ? firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 }) : firstDayOfWeek; - const end = state.viewMode.unit === 'months' ? firstDayOfMonth.add({ months: state.viewMode.value }).subtract({ days: 1 }) : firstDayOfWeek.add({ days: 6 }); - return Array.from(getChunks(generateDateRange(start, end), 7)).flat(); - }, [state.viewMode, firstDayOfMonth, firstDayOfWeek, weekStartsOn]); + const start = + state.viewMode.unit === 'months' + ? firstDayOfMonth.subtract({ + days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7, + }) + : firstDayOfWeek + + let end + if (state.viewMode.unit === 'months') { + const lastDayOfMonth = firstDayOfMonth + .add({ months: state.viewMode.value }) + .subtract({ days: 1 }) + const lastDayOfMonthWeekDay = + (lastDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 + end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }) + } else { + end = firstDayOfWeek.add({ days: 6 }) + } + + return Array.from(getChunks(generateDateRange(start, end), 7)).flat() + }, [state.viewMode, firstDayOfMonth, firstDayOfWeek, weekStartsOn]) const eventMap = useMemo(() => { const map = new Map() events?.forEach((event) => { const eventStartDate = Temporal.PlainDateTime.from(event.startDate) const eventEndDate = Temporal.PlainDateTime.from(event.endDate) - if (Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0) { + if ( + Temporal.PlainDate.compare( + eventStartDate.toPlainDate(), + eventEndDate.toPlainDate(), + ) !== 0 + ) { const splitEvents = splitMultiDayEvents(event) splitEvents.forEach((splitEvent) => { const splitKey = splitEvent.startDate.toString().split('T')[0] @@ -155,103 +220,147 @@ export const useCalendar = ({ return map }, [events]) - const daysWithEvents = useMemo(() => days.map((day) => { - const dayKey = day.toString() - const dailyEvents = eventMap.get(dayKey) ?? [] - const isInCurrentPeriod = day.month === state.currPeriod.month + const daysWithEvents = useMemo( + () => + days.map((day) => { + const dayKey = day.toString() + const dailyEvents = eventMap.get(dayKey) ?? [] + const isInCurrentPeriod = day.month === state.currPeriod.month return { date: day, events: dailyEvents, isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, isInCurrentPeriod } }), [days, eventMap, state.currPeriod]) const weeks = useMemo(() => state.viewMode.unit === 'months' ? [...getChunks(daysWithEvents, 7)] : [daysWithEvents], [state.viewMode, daysWithEvents]) - const goToPreviousPeriod = useCallback(() => startTransition(() => dispatch(actions.goToPreviousPeriod({ weekStartsOn }))), [dispatch, weekStartsOn]) - - const goToNextPeriod = useCallback(() => startTransition(() => dispatch(actions.goToNextPeriod({ weekStartsOn }))), [dispatch, weekStartsOn]) - - const goToCurrentPeriod = useCallback(() => startTransition(() => dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO()))), [dispatch]) - - const goToSpecificPeriod = useCallback((date: Temporal.PlainDate) => startTransition(() => dispatch(actions.setCurrentPeriod(date))), [dispatch]) - - const changeViewMode = useCallback((newViewMode: UseCalendarState['viewMode']) => { - startTransition(() => { - dispatch(actions.setViewMode(newViewMode)); - onChangeViewMode?.(newViewMode); - }); - }, [dispatch, onChangeViewMode]); - - const getEventProps = useCallback((id: Event['id']): { style: CSSProperties } | null => { - const event = [...eventMap.values()].flat().find((currEvent) => currEvent.id === id) - if (!event) return null + const goToPreviousPeriod = useCallback( + () => + startTransition(() => + dispatch(actions.goToPreviousPeriod({ weekStartsOn })), + ), + [dispatch, weekStartsOn], + ) + + const goToNextPeriod = useCallback( + () => + startTransition(() => dispatch(actions.goToNextPeriod({ weekStartsOn }))), + [dispatch, weekStartsOn], + ) + + const goToCurrentPeriod = useCallback( + () => + startTransition(() => + dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO())), + ), + [dispatch], + ) + + const goToSpecificPeriod = useCallback( + (date: Temporal.PlainDate) => + startTransition(() => dispatch(actions.setCurrentPeriod(date))), + [dispatch], + ) + + const changeViewMode = useCallback( + (newViewMode: { value: number; unit: 'months' | 'weeks' | 'days' }) => { + startTransition(() => { + dispatch(actions.setViewMode(newViewMode)) + onChangeViewMode?.(newViewMode) + }) + }, + [dispatch, onChangeViewMode], + ) + + const getEventProps = useCallback( + (id: Event['id']): { style: CSSProperties } | null => { + const event = [...eventMap.values()] + .flat() + .find((currEvent) => currEvent.id === id) + if (!event) return null - const eventStartDate = Temporal.PlainDateTime.from(event.startDate) - const eventEndDate = Temporal.PlainDateTime.from(event.endDate) - const isSplitEvent = Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0 - - let percentageOfDay - let eventHeightInMinutes - - if (isSplitEvent) { - const isStartPart = eventStartDate.hour !== 0 || eventStartDate.minute !== 0 - if (isStartPart) { - const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 - eventHeightInMinutes = 24 * 60 - eventTimeInMinutes + const eventStartDate = Temporal.PlainDateTime.from(event.startDate) + const eventEndDate = Temporal.PlainDateTime.from(event.endDate) + const isSplitEvent = + Temporal.PlainDate.compare( + eventStartDate.toPlainDate(), + eventEndDate.toPlainDate(), + ) !== 0 + + let percentageOfDay + let eventHeightInMinutes + + if (isSplitEvent) { + const isStartPart = + eventStartDate.hour !== 0 || eventStartDate.minute !== 0 + if (isStartPart) { + const eventTimeInMinutes = + eventStartDate.hour * 60 + eventStartDate.minute + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 + eventHeightInMinutes = 24 * 60 - eventTimeInMinutes + } else { + percentageOfDay = 0 + eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute + } } else { - percentageOfDay = 0 - eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute + const eventTimeInMinutes = + eventStartDate.hour * 60 + eventStartDate.minute + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 + const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute + eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes } - } else { - const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 - const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute - eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes - } - - const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20) - - const overlappingEvents = [...eventMap.values()].flat().filter((e) => { - const eStartDate = Temporal.PlainDateTime.from(e.startDate) - const eEndDate = Temporal.PlainDateTime.from(e.endDate) - return ( - (e.id !== id && - Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && - Temporal.PlainDateTime.compare(eventStartDate, eEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eventEndDate, eStartDate) >= 0 && - Temporal.PlainDateTime.compare(eventEndDate, eEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eStartDate, eventStartDate) >= 0 && - Temporal.PlainDateTime.compare(eStartDate, eventEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eEndDate, eventStartDate) >= 0 && - Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) - ) - }) - const eventIndex = overlappingEvents.findIndex((e) => e.id === id) - const totalOverlaps = overlappingEvents.length - const sidePadding = 2 - const innerPadding = 2 - const totalInnerPadding = (totalOverlaps - 1) * innerPadding - const availableWidth = 100 - totalInnerPadding - 2 * sidePadding - const eventWidth = totalOverlaps > 0 ? availableWidth / totalOverlaps : 100 - 2 * sidePadding - const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) - - if (state.viewMode.unit === 'weeks' || state.viewMode.unit === 'days') { - return { - style: { - position: 'absolute', - top: `min(${percentageOfDay}%, calc(100% - 55px))`, - left: `${eventLeft}%`, - width: `${eventWidth}%`, - margin: 0, - height: `${eventHeight}%`, - }, + const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20) + + const overlappingEvents = [...eventMap.values()].flat().filter((e) => { + const eStartDate = Temporal.PlainDateTime.from(e.startDate) + const eEndDate = Temporal.PlainDateTime.from(e.endDate) + return ( + (e.id !== id && + Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && + Temporal.PlainDateTime.compare(eventStartDate, eEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eventEndDate, eStartDate) >= 0 && + Temporal.PlainDateTime.compare(eventEndDate, eEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eStartDate, eventStartDate) >= 0 && + Temporal.PlainDateTime.compare(eStartDate, eventEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eEndDate, eventStartDate) >= 0 && + Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) + ) + }) + + const eventIndex = overlappingEvents.findIndex((e) => e.id === id) + const totalOverlaps = overlappingEvents.length + const sidePadding = 2 + const innerPadding = 2 + const totalInnerPadding = (totalOverlaps - 1) * innerPadding + const availableWidth = 100 - totalInnerPadding - 2 * sidePadding + const eventWidth = + totalOverlaps > 0 + ? availableWidth / totalOverlaps + : 100 - 2 * sidePadding + const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) + + if (state.viewMode.unit === 'weeks' || state.viewMode.unit === 'days') { + return { + style: { + position: 'absolute', + top: `min(${percentageOfDay}%, calc(100% - 55px))`, + left: `${eventLeft}%`, + width: `${eventWidth}%`, + margin: 0, + height: `${eventHeight}%`, + }, + } } - } - - return null - }, [eventMap, state.viewMode]) + + return null + }, + [eventMap, state.viewMode], + ) useEffect(() => { - const intervalId = setInterval(() => dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())), 60000) + const intervalId = setInterval( + () => + dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())), + 60000, + ) return () => clearInterval(intervalId) }, [dispatch]) @@ -273,14 +382,20 @@ export const useCalendar = ({ const daysNames = useMemo(() => { const baseDate = Temporal.PlainDate.from('2024-01-01') return Array.from({ length: 7 }).map((_, i) => - baseDate.add({ days: (i + weekStartsOn - 1) % 7 }) - .toLocaleString(locale, { weekday: 'short' }) + baseDate + .add({ days: (i + weekStartsOn - 1) % 7 }) + .toLocaleString(locale, { weekday: 'short' }), ) }, [locale, weekStartsOn]) return { ...state, - firstDayOfPeriod: state.viewMode.unit === 'months' ? firstDayOfMonth : state.viewMode.unit === 'weeks' ? firstDayOfWeek : state.currPeriod, + firstDayOfPeriod: + state.viewMode.unit === 'months' + ? firstDayOfMonth + : state.viewMode.unit === 'weeks' + ? firstDayOfWeek + : state.currPeriod, goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, @@ -290,6 +405,6 @@ export const useCalendar = ({ changeViewMode, getEventProps, currentTimeMarkerProps, - isPending + isPending, } -} \ No newline at end of file +} From 0f05c00683774e4128a58d991b83773d9beac605 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 13:16:42 +0200 Subject: [PATCH 045/128] refactor: api --- .../react-time/src/tests/useCalendar.test.tsx | 89 ++-- .../react-time/src/useCalendar/useCalendar.ts | 435 +++++++++++------- .../src/useCalendar/useCalendarReducer.ts | 26 +- .../src/useCalendar/useCalendarState.ts | 2 +- 4 files changed, 348 insertions(+), 204 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index bfecedc..c7753ce 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -1,11 +1,14 @@ import { Temporal } from '@js-temporal/polyfill' -import { afterEach, describe, expect, test, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' import { useCalendar } from '../useCalendar' -import type { UseCalendarAction} from '../useCalendar/calendarActions'; +import type { UseCalendarAction } from '../useCalendar/calendarActions'; import type { UseCalendarState } from '../useCalendar/useCalendarState'; describe('useCalendar', () => { + beforeEach(() => { + vi.setSystemTime(new Date('2024-06-01T11:00:00')); + }); afterEach(() => { vi.useRealTimers(); }); @@ -30,7 +33,7 @@ describe('useCalendar', () => { useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), ) expect(result.current.viewMode).toEqual({ value: 1, unit: 'months' }) - expect(result.current.currPeriod.toString()).toBe( + expect(result.current.currentPeriod.toString()).toBe( Temporal.Now.plainDateISO().toString(), ) }) @@ -47,14 +50,9 @@ describe('useCalendar', () => { const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ months: 1, }) - const expectedFirstDayOfPreviousMonth = Temporal.PlainDate.from({ - year: expectedPreviousMonth.year, - month: expectedPreviousMonth.month, - day: 1, - }) - expect(result.current.firstDayOfPeriod).toEqual( - expectedFirstDayOfPreviousMonth, + expect(result.current.currentPeriod).toEqual( + expectedPreviousMonth, ) }) @@ -68,13 +66,8 @@ describe('useCalendar', () => { }) const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) - const expectedFirstDayOfNextMonth = Temporal.PlainDate.from({ - year: expectedNextMonth.year, - month: expectedNextMonth.month, - day: 1, - }) - expect(result.current.firstDayOfPeriod).toEqual(expectedFirstDayOfNextMonth) + expect(result.current.currentPeriod).toEqual(expectedNextMonth) }) test('should change view mode correctly', () => { @@ -98,7 +91,7 @@ describe('useCalendar', () => { result.current.goToSpecificPeriod(Temporal.PlainDate.from('2024-06-01')) }) - expect(result.current.currPeriod.toString()).toBe('2024-06-01') + expect(result.current.currentPeriod.toString()).toBe('2024-06-01') }) test('should return the correct props for an event', () => { @@ -166,8 +159,6 @@ describe('useCalendar', () => { }) test('should return the correct props for the current time marker', () => { - vi.setSystemTime(new Date('2024-06-01T11:00:00')); - const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'weeks' } }), ) @@ -185,13 +176,13 @@ describe('useCalendar', () => { }) test('should render array of days', () => { - vi.setSystemTime(new Date('2024-06-01T11:00:00')); - const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }), ); - const { weeks } = result.current; + const { days } = result.current; + const weeks = result.current.groupDaysBy(days, 'weeks'); + expect(weeks).toHaveLength(5); expect(weeks[0]).toHaveLength(7); @@ -217,12 +208,12 @@ describe('useCalendar', () => { }); test('should correctly mark days as in current period', () => { - vi.setSystemTime(new Date('2024-06-01T11:00:00')); const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) ); - const { weeks } = result.current; + const { days } = result.current; + const weeks = result.current.groupDaysBy(days, 'weeks'); const daysInCurrentPeriod = weeks.flat().map(day => day.isInCurrentPeriod); expect(daysInCurrentPeriod).toEqual([ @@ -242,7 +233,7 @@ describe('useCalendar', () => { result.current.goToSpecificPeriod(specificDate) }) - expect(result.current.currPeriod).toEqual(specificDate) + expect(result.current.currentPeriod).toEqual(specificDate) }) test('should navigate to the previous period correctly', () => { @@ -258,7 +249,7 @@ describe('useCalendar', () => { months: 1, }) - expect(result.current.currPeriod).toEqual(expectedPreviousMonth) + expect(result.current.currentPeriod).toEqual(expectedPreviousMonth) }) test('should navigate to the next period correctly', () => { @@ -272,7 +263,7 @@ describe('useCalendar', () => { const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) - expect(result.current.currPeriod).toEqual(expectedNextMonth) + expect(result.current.currentPeriod).toEqual(expectedNextMonth) }) test('should reset to the current period correctly', () => { @@ -285,7 +276,7 @@ describe('useCalendar', () => { result.current.goToCurrentPeriod() }) - expect(result.current.currPeriod).toEqual( + expect(result.current.currentPeriod).toEqual( Temporal.Now.plainDateISO(), ) }) @@ -295,7 +286,7 @@ describe('useCalendar', () => { if (action.type === 'SET_NEXT_PERIOD') { return { ...state, - currPeriod: state.currPeriod.add({ months: 2 }), + currentPeriod: state.currentPeriod.add({ months: 2 }), } } @@ -311,6 +302,44 @@ describe('useCalendar', () => { }) const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 2 }) - expect(result.current.currPeriod).toEqual(expectedNextMonth) + expect(result.current.currentPeriod).toEqual(expectedNextMonth) + }); + + test('should group days by months correctly', () => { + const { result } = renderHook(() => + useCalendar({ events, viewMode: { value: 2, unit: 'months' }, locale: 'en-US' }) + ); + + const { days, groupDaysBy } = result.current; + const months = groupDaysBy(days, 'months'); + + expect(months).toHaveLength(2); + expect(months[0]?.[0]?.date.toString()).toBe('2024-06-01'); + expect(months[1]?.[0]?.date.toString()).toBe('2024-07-01'); + }); + + test('should group days by weeks correctly', () => { + const { result } = renderHook(() => + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) + ); + + const { days, groupDaysBy } = result.current; + const weeks = groupDaysBy(days, 'weeks'); + expect(weeks).toHaveLength(5); + expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-27'); + expect(weeks[4]?.[6]?.date.toString()).toBe('2024-06-30'); + }); + + test('should group days by weeks correctly when weekStartsOn is Sunday', () => { + const { result } = renderHook(() => + useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US', weekStartsOn: 7 }) + ); + + const { days, groupDaysBy } = result.current; + const weeks = groupDaysBy(days, 'weeks'); + + expect(weeks).toHaveLength(6); + expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-26'); + expect(weeks[4]?.[6]?.date.toString()).toBe('2024-06-29'); }); }); diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 06baefa..082a9f3 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,66 +1,53 @@ -import type { CSSProperties } from 'react' import { useCallback, useEffect, useMemo, useTransition } from 'react' import { Temporal } from '@js-temporal/polyfill' -import type { UseCalendarAction } from './calendarActions' -import type { Event, UseCalendarState } from './useCalendarState' +import { getFirstDayOfMonth, getFirstDayOfWeek } from '@tanstack/time' import { actions } from './calendarActions' import { useCalendarReducer } from './useCalendarReducer' - -export const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { - const date = Temporal.PlainDate.from(currWeek) - return date.subtract({ days: (date.dayOfWeek - weekStartsOn + 7) % 7 }) -} - -export const getFirstDayOfMonth = (currMonth: string) => - Temporal.PlainDate.from(`${currMonth}-01`) +import type { UseCalendarAction } from './calendarActions' +import type { Event, UseCalendarState } from './useCalendarState' +import type { CSSProperties } from 'react' interface UseCalendarProps< TEvent extends Event, TState extends UseCalendarState = UseCalendarState, > { /** - * The day of the week the calendar should start on (1 for Monday, 7 for Sunday). + * The day of the week the calendar should start on (0 for Sunday, 6 for Saturday). * @default 1 */ - weekStartsOn?: number + weekStartsOn?: number; /** * An array of events that the calendar should display. */ - events?: TEvent[] + events?: TEvent[]; /** * The initial view mode of the calendar. It can be 'month', 'week', or a number representing the number of days in a custom view mode. */ - viewMode: UseCalendarState['viewMode'] + viewMode: UseCalendarState['viewMode']; /** * The locale to use for formatting dates and times. */ - locale?: Parameters['0'] + locale?: Parameters['0']; /** * Callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. */ - onChangeViewMode?: (viewMode: UseCalendarState['viewMode']) => void + onChangeViewMode?: (viewMode: UseCalendarState['viewMode']) => void; /** * Custom reducer function to manage the state of the calendar. */ reducer?: ( state: TState, action: TAction, - ) => TState -} - -const getChunks = function* (arr: T[], n: number) { - for (let i = 0; i < arr.length; i += n) { - yield arr.slice(i, i + n) - } + ) => TState; } const splitMultiDayEvents = (event: TEvent): TEvent[] => { - const startDate = Temporal.PlainDateTime.from(event.startDate) - const endDate = Temporal.PlainDateTime.from(event.endDate) - const events: TEvent[] = [] + const startDate = Temporal.PlainDateTime.from(event.startDate); + const endDate = Temporal.PlainDateTime.from(event.endDate); + const events: TEvent[] = []; - let currentDay = startDate + let currentDay = startDate; while ( Temporal.PlainDate.compare( currentDay.toPlainDate(), @@ -72,42 +59,42 @@ const splitMultiDayEvents = (event: TEvent): TEvent[] => { minute: 0, second: 0, millisecond: 0, - }) + }); const endOfCurrentDay = currentDay.with({ hour: 23, minute: 59, second: 59, millisecond: 999, - }) + }); const eventStart = Temporal.PlainDateTime.compare(currentDay, startDate) === 0 ? startDate - : startOfCurrentDay + : startOfCurrentDay; const eventEnd = Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 ? endDate - : endOfCurrentDay + : endOfCurrentDay; - events.push({ ...event, startDate: eventStart, endDate: eventEnd }) + events.push({ ...event, startDate: eventStart, endDate: eventEnd }); - currentDay = startOfCurrentDay.add({ days: 1 }) + currentDay = startOfCurrentDay.add({ days: 1 }); } - return events + return events; } const generateDateRange = ( start: Temporal.PlainDate, end: Temporal.PlainDate, ) => { - const dates: Temporal.PlainDate[] = [] - let current = start + const dates: Temporal.PlainDate[] = []; + let current = start; while (Temporal.PlainDate.compare(current, end) <= 0) { - dates.push(current) - current = current.add({ days: 1 }) + dates.push(current); + current = current.add({ days: 1 }); } - return dates + return dates; } /** @@ -122,13 +109,12 @@ const generateDateRange = ( * @param {Function} [props.reducer] - Custom reducer function to manage the state of the calendar. * * @returns {Object} calendarState - The state and functions for managing the calendar. - * @returns {Temporal.PlainDate} calendarState.firstDayOfPeriod - The first day of the current period displayed by the calendar. - * @returns {Temporal.PlainDate} calendarState.currPeriod - The current period displayed by the calendar. + * @returns {Temporal.PlainDate} calendarState.currentPeriod - The current period displayed by the calendar. * @returns {Function} calendarState.goToPreviousPeriod - Function to navigate to the previous period. * @returns {Function} calendarState.goToNextPeriod - Function to navigate to the next period. * @returns {Function} calendarState.goToCurrentPeriod - Function to navigate to the current period. * @returns {Function} calendarState.goToSpecificPeriod - Function to navigate to a specific period. - * @returns {Array>} calendarState.weeks - The calendar grid, where each cell contains the date and events for that day. + * @returns {Array>} calendarState.days - The calendar grid, where each cell contains the date and events for that day. * @returns {string[]} calendarState.daysNames - An array of day names based on the locale and week start day. * @returns {'month' | 'week' | number} calendarState.viewMode - The current view mode of the calendar. * @returns {Function} calendarState.changeViewMode - Function to change the view mode of the calendar. @@ -143,93 +129,126 @@ export const useCalendar = ({ onChangeViewMode, reducer, }: UseCalendarProps) => { - const today = Temporal.Now.plainDateISO() - const [isPending, startTransition] = useTransition() + const today = Temporal.Now.plainDateISO(); + const [isPending, startTransition] = useTransition(); const [state, dispatch] = useCalendarReducer( { - currPeriod: today, + currentPeriod: today, viewMode: initialViewMode, currentTime: Temporal.Now.plainDateTimeISO(), }, reducer, - ) + ); const firstDayOfMonth = useMemo( () => getFirstDayOfMonth( - state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7), + state.currentPeriod.toString({ calendarName: "auto" }).substring(0, 7), ), - [state.currPeriod], - ) + [state.currentPeriod], + ); const firstDayOfWeek = useMemo( - () => getFirstDayOfWeek(state.currPeriod.toString(), weekStartsOn), - [state.currPeriod, weekStartsOn], - ) + () => getFirstDayOfWeek(state.currentPeriod.toString(), weekStartsOn), + [state.currentPeriod, weekStartsOn], + ); - const days = useMemo(() => { + const calendarDays = useMemo(() => { const start = - state.viewMode.unit === 'months' + state.viewMode.unit === "months" ? firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7, }) - : firstDayOfWeek - - let end - if (state.viewMode.unit === 'months') { - const lastDayOfMonth = firstDayOfMonth - .add({ months: state.viewMode.value }) - .subtract({ days: 1 }) - const lastDayOfMonthWeekDay = - (lastDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 - end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }) - } else { - end = firstDayOfWeek.add({ days: 6 }) + : state.currentPeriod; + + let end; + switch (state.viewMode.unit) { + case "months": { + const lastDayOfMonth = firstDayOfMonth + .add({ months: state.viewMode.value }) + .subtract({ days: 1 }); + const lastDayOfMonthWeekDay = + (lastDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7; + end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }); + break; + } + case "weeks": { + end = firstDayOfWeek.add({ days: 7 * state.viewMode.value - 1 }); + break; + } + case "days": { + end = state.currentPeriod.add({ days: state.viewMode.value - 1 }); + break; + } } - return Array.from(getChunks(generateDateRange(start, end), 7)).flat() - }, [state.viewMode, firstDayOfMonth, firstDayOfWeek, weekStartsOn]) + const allDays = generateDateRange(start, end); + const startMonth = state.currentPeriod.month; + const endMonth = state.currentPeriod.add({ + months: state.viewMode.value - 1, + }).month; + + return allDays.filter( + (day) => day.month >= startMonth && day.month <= endMonth, + ); + }, [ + state.viewMode, + firstDayOfMonth, + firstDayOfWeek, + weekStartsOn, + state.currentPeriod, + ]); const eventMap = useMemo(() => { - const map = new Map() + const map = new Map(); events?.forEach((event) => { - const eventStartDate = Temporal.PlainDateTime.from(event.startDate) - const eventEndDate = Temporal.PlainDateTime.from(event.endDate) + const eventStartDate = Temporal.PlainDateTime.from(event.startDate); + const eventEndDate = Temporal.PlainDateTime.from(event.endDate); if ( Temporal.PlainDate.compare( eventStartDate.toPlainDate(), eventEndDate.toPlainDate(), ) !== 0 ) { - const splitEvents = splitMultiDayEvents(event) + const splitEvents = splitMultiDayEvents(event); splitEvents.forEach((splitEvent) => { - const splitKey = splitEvent.startDate.toString().split('T')[0] + const splitKey = splitEvent.startDate.toString().split("T")[0]; if (splitKey) { - if (!map.has(splitKey)) map.set(splitKey, []) - map.get(splitKey)?.push(splitEvent) + if (!map.has(splitKey)) map.set(splitKey, []); + map.get(splitKey)?.push(splitEvent); } - }) + }); } else { - const eventKey = event.startDate.toString().split('T')[0] + const eventKey = event.startDate.toString().split("T")[0]; if (eventKey) { - if (!map.has(eventKey)) map.set(eventKey, []) - map.get(eventKey)?.push(event) + if (!map.has(eventKey)) map.set(eventKey, []); + map.get(eventKey)?.push(event); } } - }) - return map - }, [events]) + }); + return map; + }, [events]); const daysWithEvents = useMemo( () => - days.map((day) => { - const dayKey = day.toString() - const dailyEvents = eventMap.get(dayKey) ?? [] - const isInCurrentPeriod = day.month === state.currPeriod.month - return { date: day, events: dailyEvents, isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, isInCurrentPeriod } - }), [days, eventMap, state.currPeriod]) - - const weeks = useMemo(() => state.viewMode.unit === 'months' ? [...getChunks(daysWithEvents, 7)] : [daysWithEvents], [state.viewMode, daysWithEvents]) + calendarDays.map((day) => { + const dayKey = day.toString(); + const dailyEvents = eventMap.get(dayKey) ?? []; + const currentMonthRange = Array.from( + { length: state.viewMode.value }, + (_, i) => state.currentPeriod.add({ months: i }).month, + ); + const isInCurrentPeriod = currentMonthRange.includes(day.month); + return { + date: day, + events: dailyEvents, + isToday: + Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, + isInCurrentPeriod, + }; + }), + [calendarDays, eventMap, state.viewMode, state.currentPeriod], + ); const goToPreviousPeriod = useCallback( () => @@ -237,13 +256,13 @@ export const useCalendar = ({ dispatch(actions.goToPreviousPeriod({ weekStartsOn })), ), [dispatch, weekStartsOn], - ) + ); const goToNextPeriod = useCallback( () => startTransition(() => dispatch(actions.goToNextPeriod({ weekStartsOn }))), [dispatch, weekStartsOn], - ) + ); const goToCurrentPeriod = useCallback( () => @@ -251,67 +270,70 @@ export const useCalendar = ({ dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO())), ), [dispatch], - ) + ); const goToSpecificPeriod = useCallback( (date: Temporal.PlainDate) => startTransition(() => dispatch(actions.setCurrentPeriod(date))), [dispatch], - ) + ); const changeViewMode = useCallback( - (newViewMode: { value: number; unit: 'months' | 'weeks' | 'days' }) => { + (newViewMode: { value: number; unit: "months" | "weeks" | "days" }) => { startTransition(() => { - dispatch(actions.setViewMode(newViewMode)) - onChangeViewMode?.(newViewMode) - }) + dispatch(actions.setViewMode(newViewMode)); + onChangeViewMode?.(newViewMode); + }); }, [dispatch, onChangeViewMode], - ) + ); const getEventProps = useCallback( - (id: Event['id']): { style: CSSProperties } | null => { + (id: Event["id"]): { style: CSSProperties } | null => { const event = [...eventMap.values()] .flat() - .find((currEvent) => currEvent.id === id) - if (!event) return null + .find((currEvent) => currEvent.id === id); + if (!event) return null; - const eventStartDate = Temporal.PlainDateTime.from(event.startDate) - const eventEndDate = Temporal.PlainDateTime.from(event.endDate) + const eventStartDate = Temporal.PlainDateTime.from(event.startDate); + const eventEndDate = Temporal.PlainDateTime.from(event.endDate); const isSplitEvent = Temporal.PlainDate.compare( eventStartDate.toPlainDate(), eventEndDate.toPlainDate(), - ) !== 0 + ) !== 0; - let percentageOfDay - let eventHeightInMinutes + let percentageOfDay; + let eventHeightInMinutes; if (isSplitEvent) { const isStartPart = - eventStartDate.hour !== 0 || eventStartDate.minute !== 0 + eventStartDate.hour !== 0 || eventStartDate.minute !== 0; if (isStartPart) { const eventTimeInMinutes = - eventStartDate.hour * 60 + eventStartDate.minute - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 - eventHeightInMinutes = 24 * 60 - eventTimeInMinutes + eventStartDate.hour * 60 + eventStartDate.minute; + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100; + eventHeightInMinutes = 24 * 60 - eventTimeInMinutes; } else { - percentageOfDay = 0 - eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute + percentageOfDay = 0; + eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; } } else { const eventTimeInMinutes = - eventStartDate.hour * 60 + eventStartDate.minute - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 - const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute - eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes + eventStartDate.hour * 60 + eventStartDate.minute; + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100; + const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; + eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes; } - const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20) + const eventHeight = Math.min( + (eventHeightInMinutes / (24 * 60)) * 100, + 20, + ); const overlappingEvents = [...eventMap.values()].flat().filter((e) => { - const eStartDate = Temporal.PlainDateTime.from(e.startDate) - const eEndDate = Temporal.PlainDateTime.from(e.endDate) + const eStartDate = Temporal.PlainDateTime.from(e.startDate); + const eEndDate = Temporal.PlainDateTime.from(e.endDate); return ( (e.id !== id && Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && @@ -322,89 +344,182 @@ export const useCalendar = ({ Temporal.PlainDateTime.compare(eStartDate, eventEndDate) <= 0) || (Temporal.PlainDateTime.compare(eEndDate, eventStartDate) >= 0 && Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) - ) - }) - - const eventIndex = overlappingEvents.findIndex((e) => e.id === id) - const totalOverlaps = overlappingEvents.length - const sidePadding = 2 - const innerPadding = 2 - const totalInnerPadding = (totalOverlaps - 1) * innerPadding - const availableWidth = 100 - totalInnerPadding - 2 * sidePadding + ); + }); + + const eventIndex = overlappingEvents.findIndex((e) => e.id === id); + const totalOverlaps = overlappingEvents.length; + const sidePadding = 2; + const innerPadding = 2; + const totalInnerPadding = (totalOverlaps - 1) * innerPadding; + const availableWidth = 100 - totalInnerPadding - 2 * sidePadding; const eventWidth = totalOverlaps > 0 ? availableWidth / totalOverlaps - : 100 - 2 * sidePadding - const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) + : 100 - 2 * sidePadding; + const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding); - if (state.viewMode.unit === 'weeks' || state.viewMode.unit === 'days') { + if (state.viewMode.unit === "weeks" || state.viewMode.unit === "days") { return { style: { - position: 'absolute', + position: "absolute", top: `min(${percentageOfDay}%, calc(100% - 55px))`, left: `${eventLeft}%`, width: `${eventWidth}%`, margin: 0, height: `${eventHeight}%`, }, - } + }; } - return null + return null; }, [eventMap, state.viewMode], - ) + ); useEffect(() => { const intervalId = setInterval( () => dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())), 60000, - ) - return () => clearInterval(intervalId) - }, [dispatch]) + ); + return () => clearInterval(intervalId); + }, [dispatch]); const currentTimeMarkerProps = useCallback(() => { - const { hour, minute } = state.currentTime - const currentTimeInMinutes = hour * 60 + minute - const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 + const { hour, minute } = state.currentTime; + const currentTimeInMinutes = hour * 60 + minute; + const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100; return { style: { - position: 'absolute', + position: "absolute", top: `${percentageOfDay}%`, left: 0, }, - currentTime: state.currentTime.toString().split('T')[1]?.substring(0, 5), - } - }, [state.currentTime]) + currentTime: state.currentTime.toString().split("T")[1]?.substring(0, 5), + }; + }, [state.currentTime]); const daysNames = useMemo(() => { - const baseDate = Temporal.PlainDate.from('2024-01-01') + const baseDate = Temporal.PlainDate.from("2024-01-01"); return Array.from({ length: 7 }).map((_, i) => baseDate .add({ days: (i + weekStartsOn - 1) % 7 }) - .toLocaleString(locale, { weekday: 'short' }), - ) - }, [locale, weekStartsOn]) + .toLocaleString(locale, { weekday: "short" }), + ); + }, [locale, weekStartsOn]); + + const groupDaysBy = useCallback( + ( + days: { + date: Temporal.PlainDate; + events: TEvent[]; + isToday: boolean; + isInCurrentPeriod: boolean; + }[], + unit: "months" | "weeks", + ) => { + const groups: { + date: Temporal.PlainDate; + events: TEvent[]; + isToday: boolean; + isInCurrentPeriod: boolean; + }[][] = []; + + switch (unit) { + case "months": { + let currentMonth: { + date: Temporal.PlainDate; + events: TEvent[]; + isToday: boolean; + isInCurrentPeriod: boolean; + }[] = []; + days.forEach((day) => { + if ( + currentMonth.length > 0 && + day.date.month !== currentMonth[0]?.date.month + ) { + groups.push(currentMonth); + currentMonth = []; + } + currentMonth.push(day); + }); + if (currentMonth.length > 0) { + groups.push(currentMonth); + } + break; + } + + case 'weeks': { + const weeks: { + date: Temporal.PlainDate; + events: TEvent[]; + isToday: boolean; + isInCurrentPeriod: boolean; + }[][] = []; + let currentWeek: { + date: Temporal.PlainDate; + events: TEvent[]; + isToday: boolean; + isInCurrentPeriod: boolean; + }[] = []; + + days.forEach((day) => { + if (currentWeek.length === 0 && day.date.dayOfWeek !== weekStartsOn) { + const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7; + for (let i = 0; i < dayOfWeek; i++) { + currentWeek.push({ + date: day.date.subtract({ days: dayOfWeek - i }), + events: [], + isToday: false, + isInCurrentPeriod: false, + }); + } + } + currentWeek.push(day); + if (currentWeek.length === 7) { + weeks.push(currentWeek); + currentWeek = []; + } + }); + + if (currentWeek.length > 0) { + while (currentWeek.length < 7) { + const lastDate = + currentWeek[currentWeek.length - 1]?.date ?? + Temporal.PlainDate.from("2024-01-01"); + currentWeek.push({ + date: lastDate.add({ days: 1 }), + events: [], + isToday: false, + isInCurrentPeriod: false, + }); + } + weeks.push(currentWeek); + } + + return weeks; + } + default: break + } + return groups; + }, + [weekStartsOn], + ); return { ...state, - firstDayOfPeriod: - state.viewMode.unit === 'months' - ? firstDayOfMonth - : state.viewMode.unit === 'weeks' - ? firstDayOfWeek - : state.currPeriod, goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, goToSpecificPeriod, - weeks, + days: daysWithEvents, daysNames, changeViewMode, getEventProps, currentTimeMarkerProps, isPending, - } -} + groupDaysBy, + }; +}; diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts index 793c101..2f7296b 100644 --- a/packages/react-time/src/useCalendar/useCalendarReducer.ts +++ b/packages/react-time/src/useCalendar/useCalendarReducer.ts @@ -9,7 +9,7 @@ const createCalendarReducer = (initialState: UseCalendarState) => { return createReducer(initialState) .handleAction(actions.setCurrentPeriod, (state, action) => ({ ...state, - currPeriod: action.payload, + currentPeriod: action.payload, })) .handleAction(actions.setViewMode, (state, action) => ({ ...state, @@ -20,29 +20,29 @@ const createCalendarReducer = (initialState: UseCalendarState) => { currentTime: action.payload, })) .handleAction(actions.goToPreviousPeriod, (state, action) => { - const firstDayOfMonth = getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)); - const firstDayOfWeek = getFirstDayOfWeek(state.currPeriod.toString(), action.payload.weekStartsOn); + const firstDayOfMonth = getFirstDayOfMonth(state.currentPeriod.toString({ calendarName: 'auto' }).substring(0, 7)); + const firstDayOfWeek = getFirstDayOfWeek(state.currentPeriod.toString(), action.payload.weekStartsOn); switch (state.viewMode.unit) { case 'months': { const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: state.viewMode.value }); return { ...state, - currPeriod: firstDayOfPrevMonth, + currentPeriod: firstDayOfPrevMonth, }; } case 'weeks': { const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: state.viewMode.value }); return { ...state, - currPeriod: firstDayOfPrevWeek, + currentPeriod: firstDayOfPrevWeek, }; } case 'days': { - const prevCustomStart = state.currPeriod.subtract({ days: state.viewMode.value }); + const prevCustomStart = state.currentPeriod.subtract({ days: state.viewMode.value }); return { ...state, - currPeriod: prevCustomStart, + currentPeriod: prevCustomStart, }; } default: @@ -50,29 +50,29 @@ const createCalendarReducer = (initialState: UseCalendarState) => { } }) .handleAction(actions.goToNextPeriod, (state, action) => { - const firstDayOfMonth = getFirstDayOfMonth(state.currPeriod.toString({ calendarName: 'auto' }).substring(0, 7)); - const firstDayOfWeek = getFirstDayOfWeek(state.currPeriod.toString(), action.payload.weekStartsOn); + const firstDayOfMonth = getFirstDayOfMonth(state.currentPeriod.toString({ calendarName: 'auto' }).substring(0, 7)); + const firstDayOfWeek = getFirstDayOfWeek(state.currentPeriod.toString(), action.payload.weekStartsOn); switch (state.viewMode.unit) { case 'months': { const firstDayOfNextMonth = firstDayOfMonth.add({ months: state.viewMode.value }); return { ...state, - currPeriod: firstDayOfNextMonth, + currentPeriod: firstDayOfNextMonth, }; } case 'weeks': { const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: state.viewMode.value }); return { ...state, - currPeriod: firstDayOfNextWeek, + currentPeriod: firstDayOfNextWeek, }; } case 'days': { - const nextCustomStart = state.currPeriod.add({ days: state.viewMode.value }); + const nextCustomStart = state.currentPeriod.add({ days: state.viewMode.value }); return { ...state, - currPeriod: nextCustomStart, + currentPeriod: nextCustomStart, }; } default: diff --git a/packages/react-time/src/useCalendar/useCalendarState.ts b/packages/react-time/src/useCalendar/useCalendarState.ts index b5a48fb..e530874 100644 --- a/packages/react-time/src/useCalendar/useCalendarState.ts +++ b/packages/react-time/src/useCalendar/useCalendarState.ts @@ -8,7 +8,7 @@ export interface Event { } export interface UseCalendarState { - currPeriod: Temporal.PlainDate + currentPeriod: Temporal.PlainDate viewMode: { value: number unit: 'months' | 'weeks' | 'days' From def6499c63f02511f51256c1e2da699028d0ddf5 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 13:16:50 +0200 Subject: [PATCH 046/128] refactor: api --- docs/framework/react/reference/useCalendar.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index 5454204..7f3681f 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -40,7 +40,7 @@ export function useCalendar({ - `firstDayOfPeriod: Temporal.PlainDate` - This value represents the first day of the current period displayed by the calendar. -- `currPeriod: string` +- `currentPeriod: string` - This value represents a string that describes the current period displayed by the calendar. - `goToPreviousPeriod: MouseEventHandler` - This function is a click event handler that navigates to the previous period. @@ -69,14 +69,13 @@ export function useCalendar({ - `isPending: boolean` - This value represents whether the calendar is in a pending state. - #### Example Usage ```tsx const CalendarComponent = ({ events }) => { const { firstDayOfPeriod, - currPeriod, + currentPeriod, goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, From 110e8be61fa7319ee8725434af2bf98e7d7f6648 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 14:06:53 +0200 Subject: [PATCH 047/128] fix: types --- packages/react-time/src/useCalendar/useCalendar.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 082a9f3..d8c36cb 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -121,14 +121,14 @@ const generateDateRange = ( * @returns {Function} calendarState.getEventProps - Function to retrieve the style properties for a specific event based on its ID. * @returns {Function} calendarState.currentTimeMarkerProps - Function to retrieve the style properties and current time for the current time marker. */ -export const useCalendar = ({ +export const useCalendar = ({ weekStartsOn = 1, events, viewMode: initialViewMode, locale, onChangeViewMode, reducer, -}: UseCalendarProps) => { +}: UseCalendarProps) => { const today = Temporal.Now.plainDateISO(); const [isPending, startTransition] = useTransition(); const [state, dispatch] = useCalendarReducer( @@ -136,7 +136,7 @@ export const useCalendar = ({ currentPeriod: today, viewMode: initialViewMode, currentTime: Temporal.Now.plainDateTimeISO(), - }, + } as TState, reducer, ); From 3333fcb391311cb20a52ee48a31a9fdfdea701f9 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 14:07:34 +0200 Subject: [PATCH 048/128] fix: types --- packages/react-time/src/useCalendar/useCalendar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index d8c36cb..99c5342 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -24,7 +24,7 @@ interface UseCalendarProps< /** * The initial view mode of the calendar. It can be 'month', 'week', or a number representing the number of days in a custom view mode. */ - viewMode: UseCalendarState['viewMode']; + viewMode: TState['viewMode']; /** * The locale to use for formatting dates and times. */ @@ -32,7 +32,7 @@ interface UseCalendarProps< /** * Callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. */ - onChangeViewMode?: (viewMode: UseCalendarState['viewMode']) => void; + onChangeViewMode?: (viewMode: TState['viewMode']) => void; /** * Custom reducer function to manage the state of the calendar. */ From 492405bb992af32dcb58639326b4480c8ddc94d4 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 14:25:06 +0200 Subject: [PATCH 049/128] fix: types --- packages/react-time/src/useCalendar/useCalendar.ts | 12 ++++++------ .../react-time/src/useCalendar/useCalendarReducer.ts | 12 ++++++------ .../react-time/src/useCalendar/useCalendarState.ts | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 99c5342..1c43f4b 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -155,7 +155,7 @@ export const useCalendar = { const start = - state.viewMode.unit === "months" + state.viewMode.unit === "month" ? firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7, }) @@ -163,7 +163,7 @@ export const useCalendar = { + (newViewMode: TState["viewMode"]) => { startTransition(() => { dispatch(actions.setViewMode(newViewMode)); onChangeViewMode?.(newViewMode); @@ -359,7 +359,7 @@ export const useCalendar = { const firstDayOfWeek = getFirstDayOfWeek(state.currentPeriod.toString(), action.payload.weekStartsOn); switch (state.viewMode.unit) { - case 'months': { + case 'month': { const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: state.viewMode.value }); return { ...state, currentPeriod: firstDayOfPrevMonth, }; } - case 'weeks': { + case 'week': { const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: state.viewMode.value }); return { ...state, currentPeriod: firstDayOfPrevWeek, }; } - case 'days': { + case 'day': { const prevCustomStart = state.currentPeriod.subtract({ days: state.viewMode.value }); return { ...state, @@ -54,21 +54,21 @@ const createCalendarReducer = (initialState: UseCalendarState) => { const firstDayOfWeek = getFirstDayOfWeek(state.currentPeriod.toString(), action.payload.weekStartsOn); switch (state.viewMode.unit) { - case 'months': { + case 'month': { const firstDayOfNextMonth = firstDayOfMonth.add({ months: state.viewMode.value }); return { ...state, currentPeriod: firstDayOfNextMonth, }; } - case 'weeks': { + case 'week': { const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: state.viewMode.value }); return { ...state, currentPeriod: firstDayOfNextWeek, }; } - case 'days': { + case 'day': { const nextCustomStart = state.currentPeriod.add({ days: state.viewMode.value }); return { ...state, diff --git a/packages/react-time/src/useCalendar/useCalendarState.ts b/packages/react-time/src/useCalendar/useCalendarState.ts index e530874..c5a3b10 100644 --- a/packages/react-time/src/useCalendar/useCalendarState.ts +++ b/packages/react-time/src/useCalendar/useCalendarState.ts @@ -11,7 +11,7 @@ export interface UseCalendarState { currentPeriod: Temporal.PlainDate viewMode: { value: number - unit: 'months' | 'weeks' | 'days' + unit: 'month' | 'week' | 'day' } currentTime: Temporal.PlainDateTime } From 7bee6b43520df3f0c9017e27b9928813644e4b96 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 14:25:36 +0200 Subject: [PATCH 050/128] fix: types --- .../react-time/src/tests/useCalendar.test.tsx | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index c7753ce..b0e07c8 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -30,9 +30,9 @@ describe('useCalendar', () => { test('should initialize with the correct view mode and current period', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), + useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), ) - expect(result.current.viewMode).toEqual({ value: 1, unit: 'months' }) + expect(result.current.viewMode).toEqual({ value: 1, unit: 'month' }) expect(result.current.currentPeriod.toString()).toBe( Temporal.Now.plainDateISO().toString(), ) @@ -40,7 +40,7 @@ describe('useCalendar', () => { test('should navigate to the previous period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), + useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), ) act(() => { @@ -58,7 +58,7 @@ describe('useCalendar', () => { test('should navigate to the next period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), + useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), ) act(() => { @@ -72,19 +72,19 @@ describe('useCalendar', () => { test('should change view mode correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), + useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), ) act(() => { - result.current.changeViewMode({ value: 1, unit: 'weeks' }) + result.current.changeViewMode({ value: 1, unit: 'week' }) }) - expect(result.current.viewMode).toEqual({ value: 1, unit: 'weeks' }) + expect(result.current.viewMode).toEqual({ value: 1, unit: 'week' }) }) test('should select a day correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' } }), + useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), ) act(() => { @@ -96,7 +96,7 @@ describe('useCalendar', () => { test('should return the correct props for an event', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'weeks' } }), + useCalendar({ events, viewMode: { value: 1, unit: 'week' } }), ) const eventProps = result.current.getEventProps('1') @@ -129,7 +129,7 @@ describe('useCalendar', () => { }, ] const { result } = renderHook(() => - useCalendar({ events: overlappingEvents, viewMode: { value: 1, unit: 'weeks' } }), + useCalendar({ events: overlappingEvents, viewMode: { value: 1, unit: 'week' } }), ) const event1Props = result.current.getEventProps('1') @@ -160,7 +160,7 @@ describe('useCalendar', () => { test('should return the correct props for the current time marker', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'weeks' } }), + useCalendar({ events, viewMode: { value: 1, unit: 'week' } }), ) const currentTimeMarkerProps = result.current.currentTimeMarkerProps() @@ -177,7 +177,7 @@ describe('useCalendar', () => { test('should render array of days', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }), + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }), ); const { days } = result.current; @@ -193,14 +193,14 @@ describe('useCalendar', () => { test('should return the correct day names based on weekStartsOn', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US', weekStartsOn: 1 }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US', weekStartsOn: 1 }) ); const { daysNames } = result.current; expect(daysNames).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']); const { result: resultSundayStart } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US', weekStartsOn: 7 }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US', weekStartsOn: 7 }) ); const { daysNames: sundayDaysNames } = resultSundayStart.current; @@ -209,7 +209,7 @@ describe('useCalendar', () => { test('should correctly mark days as in current period', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) ); const { days } = result.current; @@ -226,7 +226,7 @@ describe('useCalendar', () => { }); test('should navigate to a specific period correctly', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'months' } })) + const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'month' } })) const specificDate = Temporal.PlainDate.from('2024-05-15') act(() => { @@ -238,7 +238,7 @@ describe('useCalendar', () => { test('should navigate to the previous period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) ) act(() => { @@ -254,7 +254,7 @@ describe('useCalendar', () => { test('should navigate to the next period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) ) act(() => { @@ -268,7 +268,7 @@ describe('useCalendar', () => { test('should reset to the current period correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) ) act(() => { @@ -294,7 +294,7 @@ describe('useCalendar', () => { } const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, reducer: customReducer }), + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, reducer: customReducer }), ) act(() => { @@ -307,7 +307,7 @@ describe('useCalendar', () => { test('should group days by months correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 2, unit: 'months' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 2, unit: 'month' }, locale: 'en-US' }) ); const { days, groupDaysBy } = result.current; @@ -320,7 +320,7 @@ describe('useCalendar', () => { test('should group days by weeks correctly', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) ); const { days, groupDaysBy } = result.current; @@ -332,7 +332,7 @@ describe('useCalendar', () => { test('should group days by weeks correctly when weekStartsOn is Sunday', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'months' }, locale: 'en-US', weekStartsOn: 7 }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US', weekStartsOn: 7 }) ); const { days, groupDaysBy } = result.current; From 83f0724db1e932d6c6500f1b61a830196894160d Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 14:30:55 +0200 Subject: [PATCH 051/128] docs: useCalendar --- .../react-time/src/useCalendar/useCalendar.ts | 372 +++++++++--------- 1 file changed, 185 insertions(+), 187 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 1c43f4b..fc09c01 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -8,6 +8,13 @@ import type { UseCalendarAction } from './calendarActions' import type { Event, UseCalendarState } from './useCalendarState' import type { CSSProperties } from 'react' +type Day = { + date: Temporal.PlainDate + events: TEvent[] + isToday: boolean + isInCurrentPeriod: boolean +} + interface UseCalendarProps< TEvent extends Event, TState extends UseCalendarState = UseCalendarState, @@ -16,38 +23,38 @@ interface UseCalendarProps< * The day of the week the calendar should start on (0 for Sunday, 6 for Saturday). * @default 1 */ - weekStartsOn?: number; + weekStartsOn?: number /** * An array of events that the calendar should display. */ - events?: TEvent[]; + events?: TEvent[] /** * The initial view mode of the calendar. It can be 'month', 'week', or a number representing the number of days in a custom view mode. */ - viewMode: TState['viewMode']; + viewMode: TState['viewMode'] /** * The locale to use for formatting dates and times. */ - locale?: Parameters['0']; + locale?: Parameters['0'] /** * Callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. */ - onChangeViewMode?: (viewMode: TState['viewMode']) => void; + onChangeViewMode?: (viewMode: TState['viewMode']) => void /** * Custom reducer function to manage the state of the calendar. */ reducer?: ( state: TState, action: TAction, - ) => TState; + ) => TState } const splitMultiDayEvents = (event: TEvent): TEvent[] => { - const startDate = Temporal.PlainDateTime.from(event.startDate); - const endDate = Temporal.PlainDateTime.from(event.endDate); - const events: TEvent[] = []; + const startDate = Temporal.PlainDateTime.from(event.startDate) + const endDate = Temporal.PlainDateTime.from(event.endDate) + const events: TEvent[] = [] - let currentDay = startDate; + let currentDay = startDate while ( Temporal.PlainDate.compare( currentDay.toPlainDate(), @@ -59,42 +66,42 @@ const splitMultiDayEvents = (event: TEvent): TEvent[] => { minute: 0, second: 0, millisecond: 0, - }); + }) const endOfCurrentDay = currentDay.with({ hour: 23, minute: 59, second: 59, millisecond: 999, - }); + }) const eventStart = Temporal.PlainDateTime.compare(currentDay, startDate) === 0 ? startDate - : startOfCurrentDay; + : startOfCurrentDay const eventEnd = Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 ? endDate - : endOfCurrentDay; + : endOfCurrentDay - events.push({ ...event, startDate: eventStart, endDate: eventEnd }); + events.push({ ...event, startDate: eventStart, endDate: eventEnd }) - currentDay = startOfCurrentDay.add({ days: 1 }); + currentDay = startOfCurrentDay.add({ days: 1 }) } - return events; + return events } const generateDateRange = ( start: Temporal.PlainDate, end: Temporal.PlainDate, ) => { - const dates: Temporal.PlainDate[] = []; - let current = start; + const dates: Temporal.PlainDate[] = [] + let current = start while (Temporal.PlainDate.compare(current, end) <= 0) { - dates.push(current); - current = current.add({ days: 1 }); + dates.push(current) + current = current.add({ days: 1 }) } - return dates; + return dates } /** @@ -121,7 +128,10 @@ const generateDateRange = ( * @returns {Function} calendarState.getEventProps - Function to retrieve the style properties for a specific event based on its ID. * @returns {Function} calendarState.currentTimeMarkerProps - Function to retrieve the style properties and current time for the current time marker. */ -export const useCalendar = ({ +export const useCalendar = < + TEvent extends Event, + TState extends UseCalendarState = UseCalendarState, +>({ weekStartsOn = 1, events, viewMode: initialViewMode, @@ -129,8 +139,8 @@ export const useCalendar = ) => { - const today = Temporal.Now.plainDateISO(); - const [isPending, startTransition] = useTransition(); + const today = Temporal.Now.plainDateISO() + const [isPending, startTransition] = useTransition() const [state, dispatch] = useCalendarReducer( { currentPeriod: today, @@ -138,117 +148,117 @@ export const useCalendar = getFirstDayOfMonth( - state.currentPeriod.toString({ calendarName: "auto" }).substring(0, 7), + state.currentPeriod.toString({ calendarName: 'auto' }).substring(0, 7), ), [state.currentPeriod], - ); + ) const firstDayOfWeek = useMemo( () => getFirstDayOfWeek(state.currentPeriod.toString(), weekStartsOn), [state.currentPeriod, weekStartsOn], - ); + ) const calendarDays = useMemo(() => { const start = - state.viewMode.unit === "month" + state.viewMode.unit === 'month' ? firstDayOfMonth.subtract({ days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7, }) - : state.currentPeriod; + : state.currentPeriod - let end; + let end switch (state.viewMode.unit) { - case "month": { + case 'month': { const lastDayOfMonth = firstDayOfMonth .add({ months: state.viewMode.value }) - .subtract({ days: 1 }); + .subtract({ days: 1 }) const lastDayOfMonthWeekDay = - (lastDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7; - end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }); - break; + (lastDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 + end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }) + break } - case "week": { - end = firstDayOfWeek.add({ days: 7 * state.viewMode.value - 1 }); - break; + case 'week': { + end = firstDayOfWeek.add({ days: 7 * state.viewMode.value - 1 }) + break } - case "day": { - end = state.currentPeriod.add({ days: state.viewMode.value - 1 }); - break; + case 'day': { + end = state.currentPeriod.add({ days: state.viewMode.value - 1 }) + break } } - const allDays = generateDateRange(start, end); - const startMonth = state.currentPeriod.month; + const allDays = generateDateRange(start, end) + const startMonth = state.currentPeriod.month const endMonth = state.currentPeriod.add({ months: state.viewMode.value - 1, - }).month; + }).month return allDays.filter( (day) => day.month >= startMonth && day.month <= endMonth, - ); + ) }, [ state.viewMode, firstDayOfMonth, firstDayOfWeek, weekStartsOn, state.currentPeriod, - ]); + ]) const eventMap = useMemo(() => { - const map = new Map(); + const map = new Map() events?.forEach((event) => { - const eventStartDate = Temporal.PlainDateTime.from(event.startDate); - const eventEndDate = Temporal.PlainDateTime.from(event.endDate); + const eventStartDate = Temporal.PlainDateTime.from(event.startDate) + const eventEndDate = Temporal.PlainDateTime.from(event.endDate) if ( Temporal.PlainDate.compare( eventStartDate.toPlainDate(), eventEndDate.toPlainDate(), ) !== 0 ) { - const splitEvents = splitMultiDayEvents(event); + const splitEvents = splitMultiDayEvents(event) splitEvents.forEach((splitEvent) => { - const splitKey = splitEvent.startDate.toString().split("T")[0]; + const splitKey = splitEvent.startDate.toString().split('T')[0] if (splitKey) { - if (!map.has(splitKey)) map.set(splitKey, []); - map.get(splitKey)?.push(splitEvent); + if (!map.has(splitKey)) map.set(splitKey, []) + map.get(splitKey)?.push(splitEvent) } - }); + }) } else { - const eventKey = event.startDate.toString().split("T")[0]; + const eventKey = event.startDate.toString().split('T')[0] if (eventKey) { - if (!map.has(eventKey)) map.set(eventKey, []); - map.get(eventKey)?.push(event); + if (!map.has(eventKey)) map.set(eventKey, []) + map.get(eventKey)?.push(event) } } - }); - return map; - }, [events]); + }) + return map + }, [events]) const daysWithEvents = useMemo( () => calendarDays.map((day) => { - const dayKey = day.toString(); - const dailyEvents = eventMap.get(dayKey) ?? []; + const dayKey = day.toString() + const dailyEvents = eventMap.get(dayKey) ?? [] const currentMonthRange = Array.from( { length: state.viewMode.value }, (_, i) => state.currentPeriod.add({ months: i }).month, - ); - const isInCurrentPeriod = currentMonthRange.includes(day.month); + ) + const isInCurrentPeriod = currentMonthRange.includes(day.month) return { date: day, events: dailyEvents, isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, isInCurrentPeriod, - }; + } }), [calendarDays, eventMap, state.viewMode, state.currentPeriod], - ); + ) const goToPreviousPeriod = useCallback( () => @@ -256,13 +266,13 @@ export const useCalendar = startTransition(() => dispatch(actions.goToNextPeriod({ weekStartsOn }))), [dispatch, weekStartsOn], - ); + ) const goToCurrentPeriod = useCallback( () => @@ -270,70 +280,67 @@ export const useCalendar = startTransition(() => dispatch(actions.setCurrentPeriod(date))), [dispatch], - ); + ) const changeViewMode = useCallback( - (newViewMode: TState["viewMode"]) => { + (newViewMode: TState['viewMode']) => { startTransition(() => { - dispatch(actions.setViewMode(newViewMode)); - onChangeViewMode?.(newViewMode); - }); + dispatch(actions.setViewMode(newViewMode)) + onChangeViewMode?.(newViewMode) + }) }, [dispatch, onChangeViewMode], - ); + ) const getEventProps = useCallback( - (id: Event["id"]): { style: CSSProperties } | null => { + (id: Event['id']): { style: CSSProperties } | null => { const event = [...eventMap.values()] .flat() - .find((currEvent) => currEvent.id === id); - if (!event) return null; + .find((currEvent) => currEvent.id === id) + if (!event) return null - const eventStartDate = Temporal.PlainDateTime.from(event.startDate); - const eventEndDate = Temporal.PlainDateTime.from(event.endDate); + const eventStartDate = Temporal.PlainDateTime.from(event.startDate) + const eventEndDate = Temporal.PlainDateTime.from(event.endDate) const isSplitEvent = Temporal.PlainDate.compare( eventStartDate.toPlainDate(), eventEndDate.toPlainDate(), - ) !== 0; + ) !== 0 - let percentageOfDay; - let eventHeightInMinutes; + let percentageOfDay + let eventHeightInMinutes if (isSplitEvent) { const isStartPart = - eventStartDate.hour !== 0 || eventStartDate.minute !== 0; + eventStartDate.hour !== 0 || eventStartDate.minute !== 0 if (isStartPart) { const eventTimeInMinutes = - eventStartDate.hour * 60 + eventStartDate.minute; - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100; - eventHeightInMinutes = 24 * 60 - eventTimeInMinutes; + eventStartDate.hour * 60 + eventStartDate.minute + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 + eventHeightInMinutes = 24 * 60 - eventTimeInMinutes } else { - percentageOfDay = 0; - eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; + percentageOfDay = 0 + eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute } } else { const eventTimeInMinutes = - eventStartDate.hour * 60 + eventStartDate.minute; - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100; - const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; - eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes; + eventStartDate.hour * 60 + eventStartDate.minute + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 + const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute + eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes } - const eventHeight = Math.min( - (eventHeightInMinutes / (24 * 60)) * 100, - 20, - ); + const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20) const overlappingEvents = [...eventMap.values()].flat().filter((e) => { - const eStartDate = Temporal.PlainDateTime.from(e.startDate); - const eEndDate = Temporal.PlainDateTime.from(e.endDate); + const eStartDate = Temporal.PlainDateTime.from(e.startDate) + const eEndDate = Temporal.PlainDateTime.from(e.endDate) return ( (e.id !== id && Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && @@ -344,169 +351,160 @@ export const useCalendar = = 0 && Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) - ); - }); - - const eventIndex = overlappingEvents.findIndex((e) => e.id === id); - const totalOverlaps = overlappingEvents.length; - const sidePadding = 2; - const innerPadding = 2; - const totalInnerPadding = (totalOverlaps - 1) * innerPadding; - const availableWidth = 100 - totalInnerPadding - 2 * sidePadding; + ) + }) + + const eventIndex = overlappingEvents.findIndex((e) => e.id === id) + const totalOverlaps = overlappingEvents.length + const sidePadding = 2 + const innerPadding = 2 + const totalInnerPadding = (totalOverlaps - 1) * innerPadding + const availableWidth = 100 - totalInnerPadding - 2 * sidePadding const eventWidth = totalOverlaps > 0 ? availableWidth / totalOverlaps - : 100 - 2 * sidePadding; - const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding); + : 100 - 2 * sidePadding + const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) - if (state.viewMode.unit === "week" || state.viewMode.unit === "day") { + if (state.viewMode.unit === 'week' || state.viewMode.unit === 'day') { return { style: { - position: "absolute", + position: 'absolute', top: `min(${percentageOfDay}%, calc(100% - 55px))`, left: `${eventLeft}%`, width: `${eventWidth}%`, margin: 0, height: `${eventHeight}%`, }, - }; + } } - return null; + return null }, [eventMap, state.viewMode], - ); + ) useEffect(() => { const intervalId = setInterval( () => dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())), 60000, - ); - return () => clearInterval(intervalId); - }, [dispatch]); + ) + return () => clearInterval(intervalId) + }, [dispatch]) const currentTimeMarkerProps = useCallback(() => { - const { hour, minute } = state.currentTime; - const currentTimeInMinutes = hour * 60 + minute; - const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100; + const { hour, minute } = state.currentTime + const currentTimeInMinutes = hour * 60 + minute + const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 return { style: { - position: "absolute", + position: 'absolute', top: `${percentageOfDay}%`, left: 0, }, - currentTime: state.currentTime.toString().split("T")[1]?.substring(0, 5), - }; - }, [state.currentTime]); + currentTime: state.currentTime.toString().split('T')[1]?.substring(0, 5), + } + }, [state.currentTime]) const daysNames = useMemo(() => { - const baseDate = Temporal.PlainDate.from("2024-01-01"); + const baseDate = Temporal.PlainDate.from('2024-01-01') return Array.from({ length: 7 }).map((_, i) => baseDate .add({ days: (i + weekStartsOn - 1) % 7 }) - .toLocaleString(locale, { weekday: "short" }), - ); - }, [locale, weekStartsOn]); + .toLocaleString(locale, { weekday: 'short' }), + ) + }, [locale, weekStartsOn]) const groupDaysBy = useCallback( - ( - days: { - date: Temporal.PlainDate; - events: TEvent[]; - isToday: boolean; - isInCurrentPeriod: boolean; - }[], - unit: "months" | "weeks", - ) => { + (days: Day[], unit: 'months' | 'weeks') => { const groups: { - date: Temporal.PlainDate; - events: TEvent[]; - isToday: boolean; - isInCurrentPeriod: boolean; - }[][] = []; + date: Temporal.PlainDate + events: TEvent[] + isToday: boolean + isInCurrentPeriod: boolean + }[][] = [] switch (unit) { - case "months": { - let currentMonth: { - date: Temporal.PlainDate; - events: TEvent[]; - isToday: boolean; - isInCurrentPeriod: boolean; - }[] = []; + case 'months': { + let currentMonth: Day[] = [] days.forEach((day) => { if ( currentMonth.length > 0 && day.date.month !== currentMonth[0]?.date.month ) { - groups.push(currentMonth); - currentMonth = []; + groups.push(currentMonth) + currentMonth = [] } - currentMonth.push(day); - }); + currentMonth.push(day) + }) if (currentMonth.length > 0) { - groups.push(currentMonth); + groups.push(currentMonth) } - break; + break } case 'weeks': { const weeks: { - date: Temporal.PlainDate; - events: TEvent[]; - isToday: boolean; - isInCurrentPeriod: boolean; - }[][] = []; + date: Temporal.PlainDate + events: TEvent[] + isToday: boolean + isInCurrentPeriod: boolean + }[][] = [] let currentWeek: { - date: Temporal.PlainDate; - events: TEvent[]; - isToday: boolean; - isInCurrentPeriod: boolean; - }[] = []; - + date: Temporal.PlainDate + events: TEvent[] + isToday: boolean + isInCurrentPeriod: boolean + }[] = [] + days.forEach((day) => { - if (currentWeek.length === 0 && day.date.dayOfWeek !== weekStartsOn) { - const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7; + if ( + currentWeek.length === 0 && + day.date.dayOfWeek !== weekStartsOn + ) { + const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7 for (let i = 0; i < dayOfWeek; i++) { currentWeek.push({ date: day.date.subtract({ days: dayOfWeek - i }), events: [], isToday: false, isInCurrentPeriod: false, - }); + }) } } - currentWeek.push(day); + currentWeek.push(day) if (currentWeek.length === 7) { - weeks.push(currentWeek); - currentWeek = []; + weeks.push(currentWeek) + currentWeek = [] } - }); - + }) + if (currentWeek.length > 0) { while (currentWeek.length < 7) { const lastDate = currentWeek[currentWeek.length - 1]?.date ?? - Temporal.PlainDate.from("2024-01-01"); + Temporal.PlainDate.from('2024-01-01') currentWeek.push({ date: lastDate.add({ days: 1 }), events: [], isToday: false, isInCurrentPeriod: false, - }); + }) } - weeks.push(currentWeek); + weeks.push(currentWeek) } - - return weeks; + + return weeks } - default: break + default: + break } - return groups; + return groups }, [weekStartsOn], - ); + ) return { ...state, @@ -521,5 +519,5 @@ export const useCalendar = Date: Tue, 11 Jun 2024 14:31:00 +0200 Subject: [PATCH 052/128] docs: useCalendar --- docs/framework/react/reference/useCalendar.md | 132 ++++++++++++++---- 1 file changed, 104 insertions(+), 28 deletions(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index 7f3681f..a55d02e 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -50,8 +50,8 @@ export function useCalendar({ - This function is a click event handler that navigates to the current period. - `goToSpecificPeriod: (date: Temporal.PlainDate) => void` - This function is a callback function that is called when a date is selected in the calendar. It receives the selected date as an argument. -- `weeks: Array>` - - This value represents the calendar grid, where each cell contains the date and events for that day. +- `days: Day[]` + - This value represents an array of days in the current period displayed by the calendar. - `daysNames: string[]` - This value represents an array of strings that contain the names of the days of the week. - `viewMode: 'month' | 'week' | number` @@ -68,6 +68,8 @@ export function useCalendar({ - This function is used to retrieve the style properties and current time for the current time marker. - `isPending: boolean` - This value represents whether the calendar is in a pending state. +- `groupDaysBy: (days: Day[], unit: 'day' | 'week' | 'month') => Day[][]` + - This function is used to group days into units of days, weeks, or months. #### Example Usage @@ -81,14 +83,15 @@ const CalendarComponent = ({ events }) => { goToCurrentPeriod, goToSpecificPeriod, changeViewMode, - weeks, + days, daysNames, viewMode, getEventProps, currentTimeMarkerProps, + groupDaysBy, } = useCalendar({ - events, - viewMode: 'month', + weekStartsOn: 1, + viewMode: { value: 1, unit: 'month' }, locale: 'en-US', onChangeViewMode: (newViewMode) => console.log('View mode changed:', newViewMode), }); @@ -101,14 +104,58 @@ const CalendarComponent = ({ events }) => {
- - - - + + + +
- {viewMode === 'month' && ( - + {viewMode.unit === 'month' && ( + groupDaysBy(days, 'months').map((month, monthIndex) => ( + + + + + + {daysNames.map((dayName, index) => ( + + ))} + + {groupDaysBy(month, 'weeks').map((week, weekIndex) => ( + + {week.map((day) => ( + + ))} + + ))} + + )) + )} + + {viewMode.unit === 'week' && ( + {daysNames.map((dayName, index) => ( ))} - + {groupDaysBy(days, 'weeks').map((week, weekIndex) => ( + + {week.map((day) => ( + + ))} + + ))} + )} - - {weeks.map((week, weekIndex) => ( - - {week.map((day) => ( - + + {daysNames.map((dayName, index) => ( + + ))} + + + {days.map((day) => ( + ))} -
- ))} - + + )}
+ {month[0]?.date.toLocaleString('default', { month: 'long' })}{' '} + {month[0]?.date.year} +
+ {dayName} +
+
{day.date.day}
+
+ {day.events.map((event) => ( +
+ {event.title} +
+ ))} +
+
@@ -116,26 +163,56 @@ const CalendarComponent = ({ events }) => {
+
{day.date.day}
+
+ {day.events.map((event) => ( +
+ {event.title} +
+ ))} +
+
-
- {day.date.day} -
+ + {viewMode.unit === 'day' && ( +
+ {dayName} +
+
{day.date.day}
{day.events.map((event) => (
{event.title}
@@ -143,10 +220,9 @@ const CalendarComponent = ({ events }) => {
); From e598c204b5bb929c759752e6f6668454141786bb Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 15:05:39 +0200 Subject: [PATCH 053/128] fix: types --- docs/framework/react/reference/useCalendar.md | 4 ++-- packages/react-time/src/useCalendar/useCalendar.ts | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index a55d02e..b54df89 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -28,9 +28,9 @@ export function useCalendar({ - This parameter is a string that specifies the initial view mode of the calendar. It can be either 'month', 'week', or a number representing the number of days in a custom view mode. - `locale?: string` - This parameter is an optional string that specifies the locale to use for formatting dates and times. It defaults to the system locale. -- `onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void` +- `onChangeViewMode?: ({ value: number; unit: "month" | "week" | "day"; }) => void` - This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. -- `onChangeViewMode?: (viewMode: 'month' | 'week' | number) => void` +- `onChangeViewMode?: (viewMode: value: number; unit: "month" | "week" | "day";) => void` - This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. - `reducer?: (state: CalendarState, action: CalendarAction) => CalendarState` - This parameter is an optional custom reducer function that can be used to manage the state of the calendar. diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index fc09c01..6968534 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -419,12 +419,7 @@ export const useCalendar = < const groupDaysBy = useCallback( (days: Day[], unit: 'months' | 'weeks') => { - const groups: { - date: Temporal.PlainDate - events: TEvent[] - isToday: boolean - isInCurrentPeriod: boolean - }[][] = [] + const groups: Day[][] = [] switch (unit) { case 'months': { From a9665a72509801a77d1064ba4a7c083c52670829 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 19:17:02 +0200 Subject: [PATCH 054/128] refactor: add the fillMissingDays argument --- docs/framework/react/reference/useCalendar.md | 4 +- .../react-time/src/tests/useCalendar.test.tsx | 4 +- .../react-time/src/useCalendar/useCalendar.ts | 97 +++++++++---------- 3 files changed, 49 insertions(+), 56 deletions(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index b54df89..4984687 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -68,8 +68,8 @@ export function useCalendar({ - This function is used to retrieve the style properties and current time for the current time marker. - `isPending: boolean` - This value represents whether the calendar is in a pending state. -- `groupDaysBy: (days: Day[], unit: 'day' | 'week' | 'month') => Day[][]` - - This function is used to group days into units of days, weeks, or months. +- `groupDaysBy: (days: Day[], unit: 'day' | 'week' | 'month', fillMissingDays?: boolean) => Day[][]` + - This function is used to group the days in the current period by a specified unit. #### Example Usage diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index b0e07c8..7e02f13 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -188,7 +188,7 @@ describe('useCalendar', () => { expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-27'); expect(weeks[weeks.length - 1]?.[0]?.date.toString()).toBe('2024-06-24'); - expect(weeks.find((week) => week.some((day) => day.isToday))?.find((day) => day.isToday)?.date.toString()).toBe('2024-06-01'); + expect(weeks.find((week) => week.some((day) => day?.isToday))?.find((day) => day?.isToday)?.date.toString()).toBe('2024-06-01'); }); test('should return the correct day names based on weekStartsOn', () => { @@ -214,7 +214,7 @@ describe('useCalendar', () => { const { days } = result.current; const weeks = result.current.groupDaysBy(days, 'weeks'); - const daysInCurrentPeriod = weeks.flat().map(day => day.isInCurrentPeriod); + const daysInCurrentPeriod = weeks.flat().map(day => day?.isInCurrentPeriod); expect(daysInCurrentPeriod).toEqual([ false, false, false, false, false, true, true, diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 6968534..9c812c5 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -418,88 +418,81 @@ export const useCalendar = < }, [locale, weekStartsOn]) const groupDaysBy = useCallback( - (days: Day[], unit: 'months' | 'weeks') => { - const groups: Day[][] = [] - + ( + days: (Day | null)[], + unit: "months" | "weeks", + fillMissingDays = true + ) => { + const groups: (Day | null)[][] = []; + switch (unit) { - case 'months': { - let currentMonth: Day[] = [] + case "months": { + let currentMonth: (Day | null)[] = []; days.forEach((day) => { if ( currentMonth.length > 0 && - day.date.month !== currentMonth[0]?.date.month + day?.date.month !== currentMonth[0]?.date.month ) { - groups.push(currentMonth) - currentMonth = [] + groups.push(currentMonth); + currentMonth = []; } - currentMonth.push(day) - }) + currentMonth.push(day); + }); if (currentMonth.length > 0) { - groups.push(currentMonth) + groups.push(currentMonth); } - break + break; } - + case 'weeks': { - const weeks: { - date: Temporal.PlainDate - events: TEvent[] - isToday: boolean - isInCurrentPeriod: boolean - }[][] = [] - let currentWeek: { - date: Temporal.PlainDate - events: TEvent[] - isToday: boolean - isInCurrentPeriod: boolean - }[] = [] - + const weeks: (Day | null)[][] = []; + let currentWeek: (Day | null)[] = []; + days.forEach((day) => { - if ( - currentWeek.length === 0 && - day.date.dayOfWeek !== weekStartsOn - ) { - const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7 + if (currentWeek.length === 0 && day?.date.dayOfWeek !== weekStartsOn) { + if (day) { + const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7; for (let i = 0; i < dayOfWeek; i++) { - currentWeek.push({ - date: day.date.subtract({ days: dayOfWeek - i }), - events: [], - isToday: false, - isInCurrentPeriod: false, - }) + currentWeek.push(fillMissingDays ? { + date: day.date.subtract({ days: dayOfWeek - i }), + events: [], + isToday: false, + isInCurrentPeriod: false, + } : null); + } } } - currentWeek.push(day) + currentWeek.push(day); if (currentWeek.length === 7) { - weeks.push(currentWeek) - currentWeek = [] + weeks.push(currentWeek); + currentWeek = []; } - }) - + }); + if (currentWeek.length > 0) { while (currentWeek.length < 7) { const lastDate = currentWeek[currentWeek.length - 1]?.date ?? - Temporal.PlainDate.from('2024-01-01') - currentWeek.push({ + Temporal.PlainDate.from("2024-01-01"); + currentWeek.push(fillMissingDays ? { date: lastDate.add({ days: 1 }), events: [], isToday: false, isInCurrentPeriod: false, - }) + } : null); } - weeks.push(currentWeek) + weeks.push(currentWeek); } - - return weeks + + return weeks; } - default: - break + default: break } - return groups + return groups; }, [weekStartsOn], - ) + ); + return { ...state, From d9e9b883faa8ef3292b98a8f2d59f23049d6a4f5 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 22:08:50 +0200 Subject: [PATCH 055/128] refactor: add the fillMissingDays argument --- .../react-time/src/tests/useCalendar.test.tsx | 10 +- .../react-time/src/useCalendar/useCalendar.ts | 117 +++++++++++------- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 7e02f13..68a1be7 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -181,7 +181,7 @@ describe('useCalendar', () => { ); const { days } = result.current; - const weeks = result.current.groupDaysBy(days, 'weeks'); + const weeks = result.current.groupDaysBy({ days, unit: 'week' }); expect(weeks).toHaveLength(5); expect(weeks[0]).toHaveLength(7); @@ -213,7 +213,7 @@ describe('useCalendar', () => { ); const { days } = result.current; - const weeks = result.current.groupDaysBy(days, 'weeks'); + const weeks = result.current.groupDaysBy({ days, unit: 'week' }); const daysInCurrentPeriod = weeks.flat().map(day => day?.isInCurrentPeriod); expect(daysInCurrentPeriod).toEqual([ @@ -311,7 +311,7 @@ describe('useCalendar', () => { ); const { days, groupDaysBy } = result.current; - const months = groupDaysBy(days, 'months'); + const months = groupDaysBy({ days, unit: 'month' }); expect(months).toHaveLength(2); expect(months[0]?.[0]?.date.toString()).toBe('2024-06-01'); @@ -324,7 +324,7 @@ describe('useCalendar', () => { ); const { days, groupDaysBy } = result.current; - const weeks = groupDaysBy(days, 'weeks'); + const weeks = groupDaysBy({ days, unit: 'week' }); expect(weeks).toHaveLength(5); expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-27'); expect(weeks[4]?.[6]?.date.toString()).toBe('2024-06-30'); @@ -336,7 +336,7 @@ describe('useCalendar', () => { ); const { days, groupDaysBy } = result.current; - const weeks = groupDaysBy(days, 'weeks'); + const weeks = groupDaysBy({ days, unit: 'week' }); expect(weeks).toHaveLength(6); expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-26'); diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 9c812c5..72c12c1 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -418,81 +418,102 @@ export const useCalendar = < }, [locale, weekStartsOn]) const groupDaysBy = useCallback( - ( - days: (Day | null)[], - unit: "months" | "weeks", - fillMissingDays = true - ) => { - const groups: (Day | null)[][] = []; - + ({ + days, + unit, + fillMissingDays = true, + }: + | { + days: (Day | null)[] + unit: 'month' + fillMissingDays?: never + } + | { + days: (Day | null)[] + unit: 'week' + fillMissingDays?: boolean + }) => { + const groups: (Day | null)[][] = [] + switch (unit) { - case "months": { - let currentMonth: (Day | null)[] = []; + case 'month': { + let currentMonth: (Day | null)[] = [] days.forEach((day) => { if ( currentMonth.length > 0 && day?.date.month !== currentMonth[0]?.date.month ) { - groups.push(currentMonth); - currentMonth = []; + groups.push(currentMonth) + currentMonth = [] } - currentMonth.push(day); - }); + currentMonth.push(day) + }) if (currentMonth.length > 0) { - groups.push(currentMonth); + groups.push(currentMonth) } - break; + break } - - case 'weeks': { - const weeks: (Day | null)[][] = []; - let currentWeek: (Day | null)[] = []; - + + case 'week': { + const weeks: (Day | null)[][] = [] + let currentWeek: (Day | null)[] = [] + days.forEach((day) => { - if (currentWeek.length === 0 && day?.date.dayOfWeek !== weekStartsOn) { + if ( + currentWeek.length === 0 && + day?.date.dayOfWeek !== weekStartsOn + ) { if (day) { - const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7; - for (let i = 0; i < dayOfWeek; i++) { - currentWeek.push(fillMissingDays ? { - date: day.date.subtract({ days: dayOfWeek - i }), - events: [], - isToday: false, - isInCurrentPeriod: false, - } : null); + const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7 + for (let i = 0; i < dayOfWeek; i++) { + currentWeek.push( + fillMissingDays + ? { + date: day.date.subtract({ days: dayOfWeek - i }), + events: [], + isToday: false, + isInCurrentPeriod: false, + } + : null, + ) } } } - currentWeek.push(day); + currentWeek.push(day) if (currentWeek.length === 7) { - weeks.push(currentWeek); - currentWeek = []; + weeks.push(currentWeek) + currentWeek = [] } - }); - + }) + if (currentWeek.length > 0) { while (currentWeek.length < 7) { const lastDate = currentWeek[currentWeek.length - 1]?.date ?? - Temporal.PlainDate.from("2024-01-01"); - currentWeek.push(fillMissingDays ? { - date: lastDate.add({ days: 1 }), - events: [], - isToday: false, - isInCurrentPeriod: false, - } : null); + Temporal.PlainDate.from('2024-01-01') + currentWeek.push( + fillMissingDays + ? { + date: lastDate.add({ days: 1 }), + events: [], + isToday: false, + isInCurrentPeriod: false, + } + : null, + ) } - weeks.push(currentWeek); + weeks.push(currentWeek) } - - return weeks; + + return weeks } - default: break + default: + break } - return groups; + return groups }, [weekStartsOn], - ); - + ) return { ...state, From fe13c2c51b601735000c0e40000bb9b26322834d Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 11 Jun 2024 22:11:03 +0200 Subject: [PATCH 056/128] refactor: add the fillMissingDays argument --- docs/framework/react/reference/useCalendar.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index 4984687..418bc27 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -68,8 +68,8 @@ export function useCalendar({ - This function is used to retrieve the style properties and current time for the current time marker. - `isPending: boolean` - This value represents whether the calendar is in a pending state. -- `groupDaysBy: (days: Day[], unit: 'day' | 'week' | 'month', fillMissingDays?: boolean) => Day[][]` - - This function is used to group the days in the current period by a specified unit. +- `groupDaysBy: ({ days: Day[], unit: 'week' | 'month', fillMissingDays?: boolean }) => Day[][]` + - This function is used to group the days in the current period by a specified unit. The `fillMissingDays` parameter can be used to fill in missing days with previous or next month's days. #### Example Usage From 9f998463e96541287245eaafbf55702cf5006a16 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 12 Jun 2024 00:57:15 +0200 Subject: [PATCH 057/128] refactor: move utilities to the core package --- docs/framework/react/reference/useCalendar.md | 4 +- .../react-time/src/tests/useCalendar.test.tsx | 4 +- .../react-time/src/useCalendar/useCalendar.ts | 260 +----------------- packages/time/package.json | 3 + .../time/src/calendar/generateDateRange.ts | 14 + .../src/calendar/getCurrentTimeMarkerProps.ts | 17 ++ packages/time/src/calendar/getEventProps.ts | 78 ++++++ packages/time/src/calendar/groupDaysBy.ts | 97 +++++++ packages/time/src/calendar/index.ts | 5 + .../time/src/calendar/splitMultiDayEvents.ts | 23 ++ packages/time/src/calendar/types.ts | 24 ++ packages/time/src/index.ts | 3 +- pnpm-lock.yaml | 4 + 13 files changed, 286 insertions(+), 250 deletions(-) create mode 100644 packages/time/src/calendar/generateDateRange.ts create mode 100644 packages/time/src/calendar/getCurrentTimeMarkerProps.ts create mode 100644 packages/time/src/calendar/getEventProps.ts create mode 100644 packages/time/src/calendar/groupDaysBy.ts create mode 100644 packages/time/src/calendar/index.ts create mode 100644 packages/time/src/calendar/splitMultiDayEvents.ts create mode 100644 packages/time/src/calendar/types.ts diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index 418bc27..0582268 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -64,7 +64,7 @@ export function useCalendar({ - This function is used to retrieve the style properties for a specific event based on its ID. - `getEventProps: (id: string) => { style: CSSProperties } | null` - This function is used to retrieve the style properties for a specific event based on its ID. -- `currentTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` +- `getCurrentTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` - This function is used to retrieve the style properties and current time for the current time marker. - `isPending: boolean` - This value represents whether the calendar is in a pending state. @@ -87,7 +87,7 @@ const CalendarComponent = ({ events }) => { daysNames, viewMode, getEventProps, - currentTimeMarkerProps, + getCurrentTimeMarkerProps, groupDaysBy, } = useCalendar({ weekStartsOn: 1, diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 68a1be7..d1f1405 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -163,9 +163,9 @@ describe('useCalendar', () => { useCalendar({ events, viewMode: { value: 1, unit: 'week' } }), ) - const currentTimeMarkerProps = result.current.currentTimeMarkerProps() + const getCurrentTimeMarkerProps = result.current.getCurrentTimeMarkerProps() - expect(currentTimeMarkerProps).toEqual({ + expect(getCurrentTimeMarkerProps).toEqual({ style: { position: 'absolute', top: '45.83333333333333%', diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 72c12c1..29adfe1 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,23 +1,18 @@ import { useCallback, useEffect, useMemo, useTransition } from 'react' import { Temporal } from '@js-temporal/polyfill' -import { getFirstDayOfMonth, getFirstDayOfWeek } from '@tanstack/time' +import { generateDateRange, getEventProps, getFirstDayOfMonth, getFirstDayOfWeek, groupDaysBy, splitMultiDayEvents } from '@tanstack/time' import { actions } from './calendarActions' import { useCalendarReducer } from './useCalendarReducer' +import type { CalendarState, Event, GroupDaysByProps} from '@tanstack/time'; import type { UseCalendarAction } from './calendarActions' -import type { Event, UseCalendarState } from './useCalendarState' import type { CSSProperties } from 'react' -type Day = { - date: Temporal.PlainDate - events: TEvent[] - isToday: boolean - isInCurrentPeriod: boolean -} + interface UseCalendarProps< TEvent extends Event, - TState extends UseCalendarState = UseCalendarState, + TState extends CalendarState = CalendarState, > { /** * The day of the week the calendar should start on (0 for Sunday, 6 for Saturday). @@ -49,61 +44,6 @@ interface UseCalendarProps< ) => TState } -const splitMultiDayEvents = (event: TEvent): TEvent[] => { - const startDate = Temporal.PlainDateTime.from(event.startDate) - const endDate = Temporal.PlainDateTime.from(event.endDate) - const events: TEvent[] = [] - - let currentDay = startDate - while ( - Temporal.PlainDate.compare( - currentDay.toPlainDate(), - endDate.toPlainDate(), - ) < 0 - ) { - const startOfCurrentDay = currentDay.with({ - hour: 0, - minute: 0, - second: 0, - millisecond: 0, - }) - const endOfCurrentDay = currentDay.with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }) - - const eventStart = - Temporal.PlainDateTime.compare(currentDay, startDate) === 0 - ? startDate - : startOfCurrentDay - const eventEnd = - Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 - ? endDate - : endOfCurrentDay - - events.push({ ...event, startDate: eventStart, endDate: eventEnd }) - - currentDay = startOfCurrentDay.add({ days: 1 }) - } - - return events -} - -const generateDateRange = ( - start: Temporal.PlainDate, - end: Temporal.PlainDate, -) => { - const dates: Temporal.PlainDate[] = [] - let current = start - while (Temporal.PlainDate.compare(current, end) <= 0) { - dates.push(current) - current = current.add({ days: 1 }) - } - return dates -} - /** * Hook to manage the state and behavior of a calendar. * @@ -130,7 +70,7 @@ const generateDateRange = ( */ export const useCalendar = < TEvent extends Event, - TState extends UseCalendarState = UseCalendarState, + TState extends CalendarState = CalendarState, >({ weekStartsOn = 1, events, @@ -220,7 +160,7 @@ export const useCalendar = < eventEndDate.toPlainDate(), ) !== 0 ) { - const splitEvents = splitMultiDayEvents(event) + const splitEvents = splitMultiDayEvents(event) splitEvents.forEach((splitEvent) => { const splitKey = splitEvent.startDate.toString().split('T')[0] if (splitKey) { @@ -298,90 +238,9 @@ export const useCalendar = < [dispatch, onChangeViewMode], ) - const getEventProps = useCallback( - (id: Event['id']): { style: CSSProperties } | null => { - const event = [...eventMap.values()] - .flat() - .find((currEvent) => currEvent.id === id) - if (!event) return null - - const eventStartDate = Temporal.PlainDateTime.from(event.startDate) - const eventEndDate = Temporal.PlainDateTime.from(event.endDate) - const isSplitEvent = - Temporal.PlainDate.compare( - eventStartDate.toPlainDate(), - eventEndDate.toPlainDate(), - ) !== 0 - - let percentageOfDay - let eventHeightInMinutes - - if (isSplitEvent) { - const isStartPart = - eventStartDate.hour !== 0 || eventStartDate.minute !== 0 - if (isStartPart) { - const eventTimeInMinutes = - eventStartDate.hour * 60 + eventStartDate.minute - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 - eventHeightInMinutes = 24 * 60 - eventTimeInMinutes - } else { - percentageOfDay = 0 - eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute - } - } else { - const eventTimeInMinutes = - eventStartDate.hour * 60 + eventStartDate.minute - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100 - const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute - eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes - } - - const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20) - - const overlappingEvents = [...eventMap.values()].flat().filter((e) => { - const eStartDate = Temporal.PlainDateTime.from(e.startDate) - const eEndDate = Temporal.PlainDateTime.from(e.endDate) - return ( - (e.id !== id && - Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && - Temporal.PlainDateTime.compare(eventStartDate, eEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eventEndDate, eStartDate) >= 0 && - Temporal.PlainDateTime.compare(eventEndDate, eEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eStartDate, eventStartDate) >= 0 && - Temporal.PlainDateTime.compare(eStartDate, eventEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eEndDate, eventStartDate) >= 0 && - Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) - ) - }) - - const eventIndex = overlappingEvents.findIndex((e) => e.id === id) - const totalOverlaps = overlappingEvents.length - const sidePadding = 2 - const innerPadding = 2 - const totalInnerPadding = (totalOverlaps - 1) * innerPadding - const availableWidth = 100 - totalInnerPadding - 2 * sidePadding - const eventWidth = - totalOverlaps > 0 - ? availableWidth / totalOverlaps - : 100 - 2 * sidePadding - const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding) - - if (state.viewMode.unit === 'week' || state.viewMode.unit === 'day') { - return { - style: { - position: 'absolute', - top: `min(${percentageOfDay}%, calc(100% - 55px))`, - left: `${eventLeft}%`, - width: `${eventWidth}%`, - margin: 0, - height: `${eventHeight}%`, - }, - } - } - - return null - }, - [eventMap, state.viewMode], + const getEventPropsCallback = useCallback( + (id: Event['id']): { style: CSSProperties } | null => getEventProps(eventMap, id, state), + [eventMap, state], ) useEffect(() => { @@ -393,7 +252,7 @@ export const useCalendar = < return () => clearInterval(intervalId) }, [dispatch]) - const currentTimeMarkerProps = useCallback(() => { + const getCurrentTimeMarkerProps = useCallback(() => { const { hour, minute } = state.currentTime const currentTimeInMinutes = hour * 60 + minute const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 @@ -417,101 +276,12 @@ export const useCalendar = < ) }, [locale, weekStartsOn]) - const groupDaysBy = useCallback( + const groupDaysByCallback = useCallback( ({ days, unit, fillMissingDays = true, - }: - | { - days: (Day | null)[] - unit: 'month' - fillMissingDays?: never - } - | { - days: (Day | null)[] - unit: 'week' - fillMissingDays?: boolean - }) => { - const groups: (Day | null)[][] = [] - - switch (unit) { - case 'month': { - let currentMonth: (Day | null)[] = [] - days.forEach((day) => { - if ( - currentMonth.length > 0 && - day?.date.month !== currentMonth[0]?.date.month - ) { - groups.push(currentMonth) - currentMonth = [] - } - currentMonth.push(day) - }) - if (currentMonth.length > 0) { - groups.push(currentMonth) - } - break - } - - case 'week': { - const weeks: (Day | null)[][] = [] - let currentWeek: (Day | null)[] = [] - - days.forEach((day) => { - if ( - currentWeek.length === 0 && - day?.date.dayOfWeek !== weekStartsOn - ) { - if (day) { - const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7 - for (let i = 0; i < dayOfWeek; i++) { - currentWeek.push( - fillMissingDays - ? { - date: day.date.subtract({ days: dayOfWeek - i }), - events: [], - isToday: false, - isInCurrentPeriod: false, - } - : null, - ) - } - } - } - currentWeek.push(day) - if (currentWeek.length === 7) { - weeks.push(currentWeek) - currentWeek = [] - } - }) - - if (currentWeek.length > 0) { - while (currentWeek.length < 7) { - const lastDate = - currentWeek[currentWeek.length - 1]?.date ?? - Temporal.PlainDate.from('2024-01-01') - currentWeek.push( - fillMissingDays - ? { - date: lastDate.add({ days: 1 }), - events: [], - isToday: false, - isInCurrentPeriod: false, - } - : null, - ) - } - weeks.push(currentWeek) - } - - return weeks - } - default: - break - } - return groups - }, + }: Omit, 'weekStartsOn'>) => groupDaysBy({ days, unit, fillMissingDays, weekStartsOn } as GroupDaysByProps), [weekStartsOn], ) @@ -524,9 +294,9 @@ export const useCalendar = < days: daysWithEvents, daysNames, changeViewMode, - getEventProps, - currentTimeMarkerProps, + getEventProps: getEventPropsCallback, + getCurrentTimeMarkerProps, isPending, - groupDaysBy, + groupDaysBy: groupDaysByCallback, } } diff --git a/packages/time/package.json b/packages/time/package.json index 1b37016..a123330 100644 --- a/packages/time/package.json +++ b/packages/time/package.json @@ -58,5 +58,8 @@ ], "dependencies": { "@js-temporal/polyfill": "^0.4.4" + }, + "devDependencies": { + "csstype": "^3.1.3" } } diff --git a/packages/time/src/calendar/generateDateRange.ts b/packages/time/src/calendar/generateDateRange.ts new file mode 100644 index 0000000..ec73960 --- /dev/null +++ b/packages/time/src/calendar/generateDateRange.ts @@ -0,0 +1,14 @@ +import { Temporal } from '@js-temporal/polyfill' + +export const generateDateRange = ( + start: Temporal.PlainDate, + end: Temporal.PlainDate, +): Temporal.PlainDate[] => { + const dates: Temporal.PlainDate[] = [] + let current = start + while (Temporal.PlainDate.compare(current, end) <= 0) { + dates.push(current) + current = current.add({ days: 1 }) + } + return dates +} diff --git a/packages/time/src/calendar/getCurrentTimeMarkerProps.ts b/packages/time/src/calendar/getCurrentTimeMarkerProps.ts new file mode 100644 index 0000000..76d17c9 --- /dev/null +++ b/packages/time/src/calendar/getCurrentTimeMarkerProps.ts @@ -0,0 +1,17 @@ +import type { Temporal } from "@js-temporal/polyfill"; +import type { Properties as CSSProperties } from "csstype"; + +export const getCurrentTimeMarkerProps = (currentTime: Temporal.PlainDateTime): { style: CSSProperties; currentTime: string | undefined } => { + const { hour, minute } = currentTime; + const currentTimeInMinutes = hour * 60 + minute; + const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100; + + return { + style: { + position: 'absolute', + top: `${percentageOfDay}%`, + left: 0, + }, + currentTime: currentTime.toString().split('T')[1]?.substring(0, 5), + }; +}; \ No newline at end of file diff --git a/packages/time/src/calendar/getEventProps.ts b/packages/time/src/calendar/getEventProps.ts new file mode 100644 index 0000000..023d59b --- /dev/null +++ b/packages/time/src/calendar/getEventProps.ts @@ -0,0 +1,78 @@ +import { Temporal } from "@js-temporal/polyfill"; +import type { Properties as CSSProperties } from "csstype"; +import type { CalendarState, Event } from "./types"; + +export const getEventProps = ( + eventMap: Map, + id: Event['id'], + state: CalendarState +): { style: CSSProperties } | null => { + const event = [...eventMap.values()].flat().find((currEvent) => currEvent.id === id); + if (!event) return null; + + const eventStartDate = Temporal.PlainDateTime.from(event.startDate); + const eventEndDate = Temporal.PlainDateTime.from(event.endDate); + const isSplitEvent = Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0; + + let percentageOfDay; + let eventHeightInMinutes; + + if (isSplitEvent) { + const isStartPart = eventStartDate.hour !== 0 || eventStartDate.minute !== 0; + if (isStartPart) { + const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute; + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100; + eventHeightInMinutes = 24 * 60 - eventTimeInMinutes; + } else { + percentageOfDay = 0; + eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; + } + } else { + const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute; + percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100; + const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; + eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes; + } + + const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20); + + const overlappingEvents = [...eventMap.values()].flat().filter((e) => { + const eStartDate = Temporal.PlainDateTime.from(e.startDate); + const eEndDate = Temporal.PlainDateTime.from(e.endDate); + return ( + (e.id !== id && + Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && + Temporal.PlainDateTime.compare(eventStartDate, eEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eventEndDate, eStartDate) >= 0 && + Temporal.PlainDateTime.compare(eventEndDate, eEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eStartDate, eventStartDate) >= 0 && + Temporal.PlainDateTime.compare(eStartDate, eventEndDate) <= 0) || + (Temporal.PlainDateTime.compare(eEndDate, eventStartDate) >= 0 && + Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) + ); + }); + + const eventIndex = overlappingEvents.findIndex((e) => e.id === id); + const totalOverlaps = overlappingEvents.length; + const sidePadding = 2; + const innerPadding = 2; + const totalInnerPadding = (totalOverlaps - 1) * innerPadding; + const availableWidth = 100 - totalInnerPadding - 2 * sidePadding; + const eventWidth = totalOverlaps > 0 ? availableWidth / totalOverlaps : 100 - 2 * sidePadding; + const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding); + + if (state.viewMode.unit === 'week' || state.viewMode.unit === 'day') { + return { + style: { + position: 'absolute', + top: `min(${percentageOfDay}%, calc(100% - 55px))`, + left: `${eventLeft}%`, + width: `${eventWidth}%`, + margin: 0, + height: `${eventHeight}%`, + }, + }; + } + + return null; +}; \ No newline at end of file diff --git a/packages/time/src/calendar/groupDaysBy.ts b/packages/time/src/calendar/groupDaysBy.ts new file mode 100644 index 0000000..8b19e98 --- /dev/null +++ b/packages/time/src/calendar/groupDaysBy.ts @@ -0,0 +1,97 @@ +import { Temporal } from "@js-temporal/polyfill"; +import type { Day, Event } from "./types"; + +interface GroupDaysByBaseProps { + days: (Day | null)[]; + weekStartsOn: number; +} + +type GroupDaysByMonthProps = GroupDaysByBaseProps & { + unit: 'month'; + fillMissingDays?: never; +}; + +type GroupDaysByWeekProps = GroupDaysByBaseProps & { + unit: 'week'; + fillMissingDays?: boolean; +}; + +export type GroupDaysByProps = GroupDaysByMonthProps | GroupDaysByWeekProps; + +export const groupDaysBy = ({ + days, + unit, + fillMissingDays = true, + weekStartsOn, +}: GroupDaysByProps): (Day | null)[][] => { + const groups: (Day | null)[][] = []; + + switch (unit) { + case 'month': { + let currentMonth: (Day | null)[] = []; + days.forEach((day) => { + if (currentMonth.length > 0 && day?.date.month !== currentMonth[0]?.date.month) { + groups.push(currentMonth); + currentMonth = []; + } + currentMonth.push(day); + }); + if (currentMonth.length > 0) { + groups.push(currentMonth); + } + break; + } + + case 'week': { + const weeks: (Day | null)[][] = []; + let currentWeek: (Day | null)[] = []; + + days.forEach((day) => { + if (currentWeek.length === 0 && day?.date.dayOfWeek !== weekStartsOn) { + if (day) { + const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7; + for (let i = 0; i < dayOfWeek; i++) { + currentWeek.push( + fillMissingDays + ? { + date: day.date.subtract({ days: dayOfWeek - i }), + events: [], + isToday: false, + isInCurrentPeriod: false, + } + : null + ); + } + } + } + currentWeek.push(day); + if (currentWeek.length === 7) { + weeks.push(currentWeek); + currentWeek = []; + } + }); + + if (currentWeek.length > 0) { + while (currentWeek.length < 7) { + const lastDate = currentWeek[currentWeek.length - 1]?.date ?? Temporal.PlainDate.from('2024-01-01'); + currentWeek.push( + fillMissingDays + ? { + date: lastDate.add({ days: 1 }), + events: [], + isToday: false, + isInCurrentPeriod: false, + } + : null + ); + } + weeks.push(currentWeek); + } + + return weeks; + } + default: + break; + } + return groups; +}; diff --git a/packages/time/src/calendar/index.ts b/packages/time/src/calendar/index.ts new file mode 100644 index 0000000..a16de01 --- /dev/null +++ b/packages/time/src/calendar/index.ts @@ -0,0 +1,5 @@ +export * from './types' +export * from './splitMultiDayEvents' +export * from './generateDateRange' +export * from './getEventProps' +export * from './groupDaysBy' \ No newline at end of file diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts new file mode 100644 index 0000000..ebb0c2a --- /dev/null +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -0,0 +1,23 @@ +import { Temporal } from '@js-temporal/polyfill'; +import type { Event } from './types'; + +export const splitMultiDayEvents = (event: TEvent): TEvent[] => { + const startDate = Temporal.PlainDateTime.from(event.startDate); + const endDate = Temporal.PlainDateTime.from(event.endDate); + const events: TEvent[] = []; + + let currentDay = startDate; + while (Temporal.PlainDate.compare(currentDay.toPlainDate(), endDate.toPlainDate()) < 0) { + const startOfCurrentDay = currentDay.with({ hour: 0, minute: 0, second: 0, millisecond: 0 }); + const endOfCurrentDay = currentDay.with({ hour: 23, minute: 59, second: 59, millisecond: 999 }); + + const eventStart = Temporal.PlainDateTime.compare(currentDay, startDate) === 0 ? startDate : startOfCurrentDay; + const eventEnd = Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 ? endDate : endOfCurrentDay; + + events.push({ ...event, startDate: eventStart, endDate: eventEnd }); + + currentDay = startOfCurrentDay.add({ days: 1 }); + } + + return events; +}; diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts new file mode 100644 index 0000000..d8552b4 --- /dev/null +++ b/packages/time/src/calendar/types.ts @@ -0,0 +1,24 @@ +import type { Temporal } from "@js-temporal/polyfill" + +export interface Event { + id: string + startDate: Temporal.PlainDateTime + endDate: Temporal.PlainDateTime + title: string +} + +export interface CalendarState { + currentPeriod: Temporal.PlainDate + viewMode: { + value: number + unit: 'month' | 'week' | 'day' + } + currentTime: Temporal.PlainDateTime +} + +export type Day = { + date: Temporal.PlainDate + events: TEvent[] + isToday: boolean + isInCurrentPeriod: boolean +} \ No newline at end of file diff --git a/packages/time/src/index.ts b/packages/time/src/index.ts index 0e36b7b..6370bcc 100644 --- a/packages/time/src/index.ts +++ b/packages/time/src/index.ts @@ -1,4 +1,5 @@ /** * TanStack Time */ -export * from './utils'; \ No newline at end of file +export * from './utils'; +export * from './calendar'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e5ac1a..7de84db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,6 +194,10 @@ importers: '@js-temporal/polyfill': specifier: ^0.4.4 version: 0.4.4 + devDependencies: + csstype: + specifier: ^3.1.3 + version: 3.1.3 packages/vue-time: dependencies: From 93b519a92c37fee040d4204a62b2825d2dd8f9e0 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 12 Jun 2024 16:42:47 +0200 Subject: [PATCH 058/128] fix: getCurrentTimeMarkerProps --- .../react-time/src/tests/useCalendar.test.tsx | 76 ++++++++++++++++--- .../react-time/src/useCalendar/useCalendar.ts | 22 ++++-- 2 files changed, 79 insertions(+), 19 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index d1f1405..2a7f6d3 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -1,18 +1,11 @@ import { Temporal } from '@js-temporal/polyfill' -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' +import { describe, expect, test, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' import { useCalendar } from '../useCalendar' import type { UseCalendarAction } from '../useCalendar/calendarActions'; import type { UseCalendarState } from '../useCalendar/useCalendarState'; describe('useCalendar', () => { - beforeEach(() => { - vi.setSystemTime(new Date('2024-06-01T11:00:00')); - }); - afterEach(() => { - vi.useRealTimers(); - }); - const events = [ { id: '1', @@ -159,11 +152,13 @@ describe('useCalendar', () => { }) test('should return the correct props for the current time marker', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2024-06-01T11:00:00')); const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'week' } }), - ) + useCalendar({ viewMode: { value: 1, unit: 'week' } }), + ); - const getCurrentTimeMarkerProps = result.current.getCurrentTimeMarkerProps() + const getCurrentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); expect(getCurrentTimeMarkerProps).toEqual({ style: { @@ -172,8 +167,65 @@ describe('useCalendar', () => { left: 0, }, currentTime: '11:00', + }); + }); + + test('should update the current time marker props after time passes', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2024-06-01T11:00:00')); + const { result } = renderHook(() => + useCalendar({ viewMode: { value: 1, unit: 'week' } }), + ); + + let currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); + + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.83333333333333%', + left: 0, + }, + currentTime: '11:00', + }); + + act(() => { + vi.useFakeTimers(); + vi.advanceTimersByTime(60000); + }); + + currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); + + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.90277777777778%', + left: 0, + }, + currentTime: '11:01', + }); + }); + + test('should update the current time marker props after time passes when the next minute is in less than a minute', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2024-06-01T11:00:55')); + + const { result } = renderHook(() => useCalendar({ viewMode: { value: 1, unit: 'week' } })); + + act(() => { + vi.advanceTimersByTime(5000); }) - }) + + const currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); + + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.90277777777778%', + left: 0, + }, + currentTime: '11:01', + }); + }); test('should render array of days', () => { const { result } = renderHook(() => diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 29adfe1..4341b1f 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -243,14 +243,22 @@ export const useCalendar = < [eventMap, state], ) + const updateCurrentTime = useCallback(() => dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())), [dispatch]) + useEffect(() => { - const intervalId = setInterval( - () => - dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())), - 60000, - ) - return () => clearInterval(intervalId) - }, [dispatch]) + const now = Temporal.Now.plainDateTimeISO(); + const msToNextMinute = (60 - now.second) * 1000 - now.millisecond; + + const timeoutId = setTimeout(() => { + updateCurrentTime(); + const intervalId = setInterval(updateCurrentTime, 60000); + + return () => clearInterval(intervalId); + }, msToNextMinute); + + return () => clearTimeout(timeoutId); + }, [dispatch, updateCurrentTime]); + const getCurrentTimeMarkerProps = useCallback(() => { const { hour, minute } = state.currentTime From 87544e219a02265e141d3eab9060eb5c182b9764 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 12 Jun 2024 18:03:24 +0200 Subject: [PATCH 059/128] docs: update jsdocs --- .../react-time/src/useCalendar/useCalendar.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 4341b1f..d5b933d 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -57,16 +57,16 @@ interface UseCalendarProps< * * @returns {Object} calendarState - The state and functions for managing the calendar. * @returns {Temporal.PlainDate} calendarState.currentPeriod - The current period displayed by the calendar. - * @returns {Function} calendarState.goToPreviousPeriod - Function to navigate to the previous period. - * @returns {Function} calendarState.goToNextPeriod - Function to navigate to the next period. - * @returns {Function} calendarState.goToCurrentPeriod - Function to navigate to the current period. - * @returns {Function} calendarState.goToSpecificPeriod - Function to navigate to a specific period. - * @returns {Array>} calendarState.days - The calendar grid, where each cell contains the date and events for that day. - * @returns {string[]} calendarState.daysNames - An array of day names based on the locale and week start day. * @returns {'month' | 'week' | number} calendarState.viewMode - The current view mode of the calendar. - * @returns {Function} calendarState.changeViewMode - Function to change the view mode of the calendar. - * @returns {Function} calendarState.getEventProps - Function to retrieve the style properties for a specific event based on its ID. - * @returns {Function} calendarState.currentTimeMarkerProps - Function to retrieve the style properties and current time for the current time marker. + * @returns {Function} goToPreviousPeriod - Function to navigate to the previous period. + * @returns {Function} goToNextPeriod - Function to navigate to the next period. + * @returns {Function} goToCurrentPeriod - Function to navigate to the current period. + * @returns {Function} goToSpecificPeriod - Function to navigate to a specific period. + * @returns {Array>} days - The calendar grid, where each cell contains the date and events for that day. + * @returns {string[]} daysNames - An array of day names based on the locale and week start day. + * @returns {Function} changeViewMode - Function to change the view mode of the calendar. + * @returns {Function} getEventProps - Function to retrieve the style properties for a specific event based on its ID. + * @returns {Function} currentTimeMarkerProps - Function to retrieve the style properties and current time for the current time marker. */ export const useCalendar = < TEvent extends Event, From b5ae21e1401eed1e628a34904acb237b8dfaa69b Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 12 Jun 2024 22:09:25 +0200 Subject: [PATCH 060/128] test: useCalendar --- .../react-time/src/tests/useCalendar.test.tsx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 2a7f6d3..7bfe44b 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -1,6 +1,6 @@ import { Temporal } from '@js-temporal/polyfill' import { describe, expect, test, vi } from 'vitest' -import { act, renderHook } from '@testing-library/react' +import { act, renderHook, waitFor } from '@testing-library/react' import { useCalendar } from '../useCalendar' import type { UseCalendarAction } from '../useCalendar/calendarActions'; import type { UseCalendarState } from '../useCalendar/useCalendarState'; @@ -177,7 +177,7 @@ describe('useCalendar', () => { useCalendar({ viewMode: { value: 1, unit: 'week' } }), ); - let currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); + const currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); expect(currentTimeMarkerProps).toEqual({ style: { @@ -189,19 +189,18 @@ describe('useCalendar', () => { }); act(() => { - vi.useFakeTimers(); vi.advanceTimersByTime(60000); }); - currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); - - expect(currentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.90277777777778%', - left: 0, - }, - currentTime: '11:01', + waitFor(() => { + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.90277777777778%', + left: 0, + }, + currentTime: '11:01', + }); }); }); From f5bd2b7cc6a1e78430e2ce05744ed1e3ac103d6b Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 12 Jun 2024 22:16:59 +0200 Subject: [PATCH 061/128] refactor: getCurrentTimeMarkerProps --- .../react-time/src/useCalendar/useCalendar.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index d5b933d..aa19d16 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useTransition } from 'react' +import { useCallback, useEffect, useMemo, useRef, useTransition } from 'react' import { Temporal } from '@js-temporal/polyfill' import { generateDateRange, getEventProps, getFirstDayOfMonth, getFirstDayOfWeek, groupDaysBy, splitMultiDayEvents } from '@tanstack/time' @@ -81,6 +81,7 @@ export const useCalendar = < }: UseCalendarProps) => { const today = Temporal.Now.plainDateISO() const [isPending, startTransition] = useTransition() + const currentTimeInterval = useRef() const [state, dispatch] = useCalendarReducer( { currentPeriod: today, @@ -246,17 +247,17 @@ export const useCalendar = < const updateCurrentTime = useCallback(() => dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())), [dispatch]) useEffect(() => { + if (currentTimeInterval.current) clearTimeout(currentTimeInterval.current); + const now = Temporal.Now.plainDateTimeISO(); const msToNextMinute = (60 - now.second) * 1000 - now.millisecond; - - const timeoutId = setTimeout(() => { + + currentTimeInterval.current = setTimeout(() => { updateCurrentTime(); - const intervalId = setInterval(updateCurrentTime, 60000); - - return () => clearInterval(intervalId); + currentTimeInterval.current = setInterval(updateCurrentTime, 60000); }, msToNextMinute); - - return () => clearTimeout(timeoutId); + + return () => clearTimeout(currentTimeInterval.current); }, [dispatch, updateCurrentTime]); From c03128c56688e6405e36d3d84d34c87667a987ad Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 13 Jun 2024 22:32:43 +0200 Subject: [PATCH 062/128] feat: calendar core --- .../react-time/src/useCalendar/useCalendar.ts | 343 +++--------------- packages/time/package.json | 3 +- packages/time/src/calendar-core.ts | 281 ++++++++++++++ packages/time/src/index.ts | 2 +- pnpm-lock.yaml | 7 + 5 files changed, 348 insertions(+), 288 deletions(-) create mode 100644 packages/time/src/calendar-core.ts diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index aa19d16..5d8acf3 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,252 +1,22 @@ -import { useCallback, useEffect, useMemo, useRef, useTransition } from 'react' -import { Temporal } from '@js-temporal/polyfill' +import { useEffect, useRef, useState, useTransition } from 'react'; +import { Temporal } from '@js-temporal/polyfill'; -import { generateDateRange, getEventProps, getFirstDayOfMonth, getFirstDayOfWeek, groupDaysBy, splitMultiDayEvents } from '@tanstack/time' -import { actions } from './calendarActions' -import { useCalendarReducer } from './useCalendarReducer' -import type { CalendarState, Event, GroupDaysByProps} from '@tanstack/time'; -import type { UseCalendarAction } from './calendarActions' -import type { CSSProperties } from 'react' +import { CalendarCore, type CalendarCoreOptions, type Event } from '@tanstack/time'; +import type { CalendarState} from '@tanstack/time'; +export const useCalendar = (options: CalendarCoreOptions) => { + const [calendarCore] = useState(() => new CalendarCore(options)); + const [state, setState] = useState(calendarCore.store.state); - -interface UseCalendarProps< - TEvent extends Event, - TState extends CalendarState = CalendarState, -> { - /** - * The day of the week the calendar should start on (0 for Sunday, 6 for Saturday). - * @default 1 - */ - weekStartsOn?: number - /** - * An array of events that the calendar should display. - */ - events?: TEvent[] - /** - * The initial view mode of the calendar. It can be 'month', 'week', or a number representing the number of days in a custom view mode. - */ - viewMode: TState['viewMode'] - /** - * The locale to use for formatting dates and times. - */ - locale?: Parameters['0'] - /** - * Callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. - */ - onChangeViewMode?: (viewMode: TState['viewMode']) => void - /** - * Custom reducer function to manage the state of the calendar. - */ - reducer?: ( - state: TState, - action: TAction, - ) => TState -} - -/** - * Hook to manage the state and behavior of a calendar. - * - * @param {UseCalendarProps} props - The configuration properties for the calendar. - * @param {number} [props.weekStartsOn=1] - The day of the week the calendar should start on (1 for Monday, 7 for Sunday). - * @param {TEvent[]} [props.events] - An array of events that the calendar should display. - * @param {'month' | 'week' | number} props.viewMode - The initial view mode of the calendar. It can be 'month', 'week', or a number representing the number of days in a custom view mode. - * @param {Intl.LocalesArgument} [props.locale] - The locale to use for formatting dates and times. - * @param {Function} [props.onChangeViewMode] - Callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. - * @param {Function} [props.reducer] - Custom reducer function to manage the state of the calendar. - * - * @returns {Object} calendarState - The state and functions for managing the calendar. - * @returns {Temporal.PlainDate} calendarState.currentPeriod - The current period displayed by the calendar. - * @returns {'month' | 'week' | number} calendarState.viewMode - The current view mode of the calendar. - * @returns {Function} goToPreviousPeriod - Function to navigate to the previous period. - * @returns {Function} goToNextPeriod - Function to navigate to the next period. - * @returns {Function} goToCurrentPeriod - Function to navigate to the current period. - * @returns {Function} goToSpecificPeriod - Function to navigate to a specific period. - * @returns {Array>} days - The calendar grid, where each cell contains the date and events for that day. - * @returns {string[]} daysNames - An array of day names based on the locale and week start day. - * @returns {Function} changeViewMode - Function to change the view mode of the calendar. - * @returns {Function} getEventProps - Function to retrieve the style properties for a specific event based on its ID. - * @returns {Function} currentTimeMarkerProps - Function to retrieve the style properties and current time for the current time marker. - */ -export const useCalendar = < - TEvent extends Event, - TState extends CalendarState = CalendarState, ->({ - weekStartsOn = 1, - events, - viewMode: initialViewMode, - locale, - onChangeViewMode, - reducer, -}: UseCalendarProps) => { - const today = Temporal.Now.plainDateISO() - const [isPending, startTransition] = useTransition() - const currentTimeInterval = useRef() - const [state, dispatch] = useCalendarReducer( - { - currentPeriod: today, - viewMode: initialViewMode, - currentTime: Temporal.Now.plainDateTimeISO(), - } as TState, - reducer, - ) - - const firstDayOfMonth = useMemo( - () => - getFirstDayOfMonth( - state.currentPeriod.toString({ calendarName: 'auto' }).substring(0, 7), - ), - [state.currentPeriod], - ) - - const firstDayOfWeek = useMemo( - () => getFirstDayOfWeek(state.currentPeriod.toString(), weekStartsOn), - [state.currentPeriod, weekStartsOn], - ) - - const calendarDays = useMemo(() => { - const start = - state.viewMode.unit === 'month' - ? firstDayOfMonth.subtract({ - days: (firstDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7, - }) - : state.currentPeriod - - let end - switch (state.viewMode.unit) { - case 'month': { - const lastDayOfMonth = firstDayOfMonth - .add({ months: state.viewMode.value }) - .subtract({ days: 1 }) - const lastDayOfMonthWeekDay = - (lastDayOfMonth.dayOfWeek - weekStartsOn + 7) % 7 - end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }) - break - } - case 'week': { - end = firstDayOfWeek.add({ days: 7 * state.viewMode.value - 1 }) - break - } - case 'day': { - end = state.currentPeriod.add({ days: state.viewMode.value - 1 }) - break - } - } - - const allDays = generateDateRange(start, end) - const startMonth = state.currentPeriod.month - const endMonth = state.currentPeriod.add({ - months: state.viewMode.value - 1, - }).month - - return allDays.filter( - (day) => day.month >= startMonth && day.month <= endMonth, - ) - }, [ - state.viewMode, - firstDayOfMonth, - firstDayOfWeek, - weekStartsOn, - state.currentPeriod, - ]) - - const eventMap = useMemo(() => { - const map = new Map() - events?.forEach((event) => { - const eventStartDate = Temporal.PlainDateTime.from(event.startDate) - const eventEndDate = Temporal.PlainDateTime.from(event.endDate) - if ( - Temporal.PlainDate.compare( - eventStartDate.toPlainDate(), - eventEndDate.toPlainDate(), - ) !== 0 - ) { - const splitEvents = splitMultiDayEvents(event) - splitEvents.forEach((splitEvent) => { - const splitKey = splitEvent.startDate.toString().split('T')[0] - if (splitKey) { - if (!map.has(splitKey)) map.set(splitKey, []) - map.get(splitKey)?.push(splitEvent) - } - }) - } else { - const eventKey = event.startDate.toString().split('T')[0] - if (eventKey) { - if (!map.has(eventKey)) map.set(eventKey, []) - map.get(eventKey)?.push(event) - } - } - }) - return map - }, [events]) - - const daysWithEvents = useMemo( - () => - calendarDays.map((day) => { - const dayKey = day.toString() - const dailyEvents = eventMap.get(dayKey) ?? [] - const currentMonthRange = Array.from( - { length: state.viewMode.value }, - (_, i) => state.currentPeriod.add({ months: i }).month, - ) - const isInCurrentPeriod = currentMonthRange.includes(day.month) - return { - date: day, - events: dailyEvents, - isToday: - Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, - isInCurrentPeriod, - } - }), - [calendarDays, eventMap, state.viewMode, state.currentPeriod], - ) - - const goToPreviousPeriod = useCallback( - () => - startTransition(() => - dispatch(actions.goToPreviousPeriod({ weekStartsOn })), - ), - [dispatch, weekStartsOn], - ) - - const goToNextPeriod = useCallback( - () => - startTransition(() => dispatch(actions.goToNextPeriod({ weekStartsOn }))), - [dispatch, weekStartsOn], - ) - - const goToCurrentPeriod = useCallback( - () => - startTransition(() => - dispatch(actions.setCurrentPeriod(Temporal.Now.plainDateISO())), - ), - [dispatch], - ) - - const goToSpecificPeriod = useCallback( - (date: Temporal.PlainDate) => - startTransition(() => dispatch(actions.setCurrentPeriod(date))), - [dispatch], - ) - - const changeViewMode = useCallback( - (newViewMode: TState['viewMode']) => { - startTransition(() => { - dispatch(actions.setViewMode(newViewMode)) - onChangeViewMode?.(newViewMode) - }) - }, - [dispatch, onChangeViewMode], - ) - - const getEventPropsCallback = useCallback( - (id: Event['id']): { style: CSSProperties } | null => getEventProps(eventMap, id, state), - [eventMap, state], - ) - - const updateCurrentTime = useCallback(() => dispatch(actions.updateCurrentTime(Temporal.Now.plainDateTimeISO())), [dispatch]) + const [isPending, startTransition] = useTransition(); + const currentTimeInterval = useRef(); useEffect(() => { + const updateCurrentTime = () => { + calendarCore.updateCurrentTime(); + setState({ ...calendarCore.store.state }); + }; + if (currentTimeInterval.current) clearTimeout(currentTimeInterval.current); const now = Temporal.Now.plainDateTimeISO(); @@ -258,41 +28,42 @@ export const useCalendar = < }, msToNextMinute); return () => clearTimeout(currentTimeInterval.current); - }, [dispatch, updateCurrentTime]); - - - const getCurrentTimeMarkerProps = useCallback(() => { - const { hour, minute } = state.currentTime - const currentTimeInMinutes = hour * 60 + minute - const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 - - return { - style: { - position: 'absolute', - top: `${percentageOfDay}%`, - left: 0, - }, - currentTime: state.currentTime.toString().split('T')[1]?.substring(0, 5), - } - }, [state.currentTime]) - - const daysNames = useMemo(() => { - const baseDate = Temporal.PlainDate.from('2024-01-01') - return Array.from({ length: 7 }).map((_, i) => - baseDate - .add({ days: (i + weekStartsOn - 1) % 7 }) - .toLocaleString(locale, { weekday: 'short' }), - ) - }, [locale, weekStartsOn]) - - const groupDaysByCallback = useCallback( - ({ - days, - unit, - fillMissingDays = true, - }: Omit, 'weekStartsOn'>) => groupDaysBy({ days, unit, fillMissingDays, weekStartsOn } as GroupDaysByProps), - [weekStartsOn], - ) + }, [calendarCore]); + + const goToPreviousPeriod = () => { + startTransition(() => { + calendarCore.goToPreviousPeriod(); + setState({ ...calendarCore.store.state }); + }); + }; + + const goToNextPeriod = () => { + startTransition(() => { + calendarCore.goToNextPeriod(); + setState({ ...calendarCore.store.state }); + }); + }; + + const goToCurrentPeriod = () => { + startTransition(() => { + calendarCore.goToCurrentPeriod(); + setState({ ...calendarCore.store.state }); + }); + }; + + const goToSpecificPeriod = (date: Temporal.PlainDate) => { + startTransition(() => { + calendarCore.goToSpecificPeriod(date); + setState({ ...calendarCore.store.state }); + }); + }; + + const changeViewMode = (newViewMode: CalendarState['viewMode']) => { + startTransition(() => { + calendarCore.changeViewMode(newViewMode); + setState({ ...calendarCore.store.state }); + }); + }; return { ...state, @@ -300,12 +71,12 @@ export const useCalendar = < goToNextPeriod, goToCurrentPeriod, goToSpecificPeriod, - days: daysWithEvents, - daysNames, + days: calendarCore.getDaysWithEvents(), + daysNames: calendarCore.getDaysNames(), changeViewMode, - getEventProps: getEventPropsCallback, - getCurrentTimeMarkerProps, + getEventProps: calendarCore.getEventProps.bind(calendarCore), + getCurrentTimeMarkerProps: calendarCore.getCurrentTimeMarkerProps.bind(calendarCore), isPending, - groupDaysBy: groupDaysByCallback, - } -} + groupDaysBy: calendarCore.groupDaysBy.bind(calendarCore), + }; +}; diff --git a/packages/time/package.json b/packages/time/package.json index a123330..44f5812 100644 --- a/packages/time/package.json +++ b/packages/time/package.json @@ -57,7 +57,8 @@ "src" ], "dependencies": { - "@js-temporal/polyfill": "^0.4.4" + "@js-temporal/polyfill": "^0.4.4", + "@tanstack/store": "^0.4.1" }, "devDependencies": { "csstype": "^3.1.3" diff --git a/packages/time/src/calendar-core.ts b/packages/time/src/calendar-core.ts new file mode 100644 index 0000000..f5ff903 --- /dev/null +++ b/packages/time/src/calendar-core.ts @@ -0,0 +1,281 @@ +import { Store } from '@tanstack/store'; +import { Temporal } from '@js-temporal/polyfill'; +import { getFirstDayOfMonth, getFirstDayOfWeek } from './utils'; +import { generateDateRange } from './calendar/generateDateRange'; +import { splitMultiDayEvents } from './calendar/splitMultiDayEvents'; +import { getEventProps } from './calendar/getEventProps'; +import { groupDaysBy } from './calendar/groupDaysBy'; +import type { GroupDaysByProps} from './calendar/groupDaysBy'; + +export interface Event { + id: string + startDate: Temporal.PlainDateTime + endDate: Temporal.PlainDateTime + title: string +} + +export interface CalendarState { + currentPeriod: Temporal.PlainDate + viewMode: { + value: number + unit: 'month' | 'week' | 'day' + } + currentTime: Temporal.PlainDateTime +} + +export type Day = { + date: Temporal.PlainDate + events: TEvent[] + isToday: boolean + isInCurrentPeriod: boolean +} + +export interface CalendarCoreOptions { + weekStartsOn: number; + events?: TEvent[]; + viewMode: CalendarState['viewMode']; + locale?: Parameters['0']; +} + +export class CalendarCore { + store: Store; + options: CalendarCoreOptions; + + constructor(options: CalendarCoreOptions) { + this.options = options; + this.store = new Store({ + currentPeriod: Temporal.Now.plainDateISO(), + viewMode: options.viewMode, + currentTime: Temporal.Now.plainDateTimeISO(), + }); + } + + private getFirstDayOfMonth() { + return getFirstDayOfMonth( + this.store.state.currentPeriod.toString({ calendarName: 'auto' }).substring(0, 7), + ); + } + + private getFirstDayOfWeek() { + return getFirstDayOfWeek(this.store.state.currentPeriod.toString(), this.options.weekStartsOn || 1); + } + + private getCalendarDays() { + const start = + this.store.state.viewMode.unit === 'month' + ? this.getFirstDayOfMonth().subtract({ + days: (this.getFirstDayOfMonth().dayOfWeek - (this.options.weekStartsOn || 1) + 7) % 7, + }) + : this.store.state.currentPeriod; + + let end; + switch (this.store.state.viewMode.unit) { + case 'month': { + const lastDayOfMonth = this.getFirstDayOfMonth() + .add({ months: this.store.state.viewMode.value }) + .subtract({ days: 1 }); + const lastDayOfMonthWeekDay = + (lastDayOfMonth.dayOfWeek - (this.options.weekStartsOn || 1) + 7) % 7; + end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }); + break; + } + case 'week': { + end = this.getFirstDayOfWeek().add({ days: 7 * this.store.state.viewMode.value - 1 }); + break; + } + case 'day': { + end = this.store.state.currentPeriod.add({ days: this.store.state.viewMode.value - 1 }); + break; + } + } + + const allDays = generateDateRange(start, end); + const startMonth = this.store.state.currentPeriod.month; + const endMonth = this.store.state.currentPeriod.add({ + months: this.store.state.viewMode.value - 1, + }).month; + + return allDays.filter( + (day) => day.month >= startMonth && day.month <= endMonth, + ); + } + + private getEventMap() { + const map = new Map(); + this.options.events?.forEach((event) => { + const eventStartDate = Temporal.PlainDateTime.from(event.startDate); + const eventEndDate = Temporal.PlainDateTime.from(event.endDate); + if ( + Temporal.PlainDate.compare( + eventStartDate.toPlainDate(), + eventEndDate.toPlainDate(), + ) !== 0 + ) { + const splitEvents = splitMultiDayEvents(event); + splitEvents.forEach((splitEvent) => { + const splitKey = splitEvent.startDate.toString().split('T')[0]; + if (splitKey) { + if (!map.has(splitKey)) map.set(splitKey, []); + map.get(splitKey)?.push(splitEvent); + } + }); + } else { + const eventKey = event.startDate.toString().split('T')[0]; + if (eventKey) { + if (!map.has(eventKey)) map.set(eventKey, []); + map.get(eventKey)?.push(event); + } + } + }); + return map; + } + + getDaysWithEvents() { + const calendarDays = this.getCalendarDays(); + const eventMap = this.getEventMap(); + return calendarDays.map((day) => { + const dayKey = day.toString(); + const dailyEvents = eventMap.get(dayKey) ?? []; + const currentMonthRange = Array.from( + { length: this.store.state.viewMode.value }, + (_, i) => this.store.state.currentPeriod.add({ months: i }).month, + ); + const isInCurrentPeriod = currentMonthRange.includes(day.month); + return { + date: day, + events: dailyEvents, + isToday: + Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, + isInCurrentPeriod, + }; + }); + } + + getDaysNames() { + const baseDate = Temporal.PlainDate.from('2024-01-01'); + return Array.from({ length: 7 }).map((_, i) => + baseDate + .add({ days: (i + (this.options.weekStartsOn || 1) - 1) % 7 }) + .toLocaleString(this.options.locale, { weekday: 'short' }), + ); + } + + changeViewMode(newViewMode: CalendarState['viewMode']) { + this.store.setState((prev) => ({ + ...prev, + viewMode: newViewMode, + })); + } + + goToPreviousPeriod() { + const firstDayOfMonth = this.getFirstDayOfMonth(); + const firstDayOfWeek = this.getFirstDayOfWeek(); + + switch (this.store.state.viewMode.unit) { + case 'month': { + const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: this.store.state.viewMode.value }); + this.store.setState((prev) => ({ + ...prev, + currentPeriod: firstDayOfPrevMonth, + })); + break; + } + + case 'week': { + const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: this.store.state.viewMode.value }); + this.store.setState((prev) => ({ + ...prev, + currentPeriod: firstDayOfPrevWeek, + })); + break; + } + + case 'day': { + const prevCustomStart = this.store.state.currentPeriod.subtract({ days: this.store.state.viewMode.value }); + this.store.setState((prev) => ({ + ...prev, + currentPeriod: prevCustomStart, + })); + break; + } + } + } + + goToNextPeriod() { + const firstDayOfMonth = this.getFirstDayOfMonth(); + const firstDayOfWeek = this.getFirstDayOfWeek(); + + switch (this.store.state.viewMode.unit) { + case 'month': { + const firstDayOfNextMonth = firstDayOfMonth.add({ months: this.store.state.viewMode.value }); + this.store.setState((prev) => ({ + ...prev, + currentPeriod: firstDayOfNextMonth, + })); + break; + } + + case 'week': { + const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: this.store.state.viewMode.value }); + this.store.setState((prev) => ({ + ...prev, + currentPeriod: firstDayOfNextWeek, + })); + break; + } + + case 'day': { + const nextCustomStart = this.store.state.currentPeriod.add({ days: this.store.state.viewMode.value }); + this.store.setState((prev) => ({ + ...prev, + currentPeriod: nextCustomStart, + })); + break; + } + } + } + + goToCurrentPeriod() { + this.store.setState((prev) => ({ + ...prev, + currentPeriod: Temporal.Now.plainDateISO(), + })); + } + + goToSpecificPeriod(date: Temporal.PlainDate) { + this.store.setState((prev) => ({ + ...prev, + currentPeriod: date, + })); + } + + updateCurrentTime() { + this.store.setState((prev) => ({ + ...prev, + currentTime: Temporal.Now.plainDateTimeISO(), + })); + } + + getEventProps(id: Event['id']) { + return getEventProps(this.getEventMap(), id, this.store.state); + } + + getCurrentTimeMarkerProps() { + const { hour, minute } = this.store.state.currentTime; + const currentTimeInMinutes = hour * 60 + minute; + const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100; + + return { + style: { + position: 'absolute', + top: `${percentageOfDay}%`, + left: 0, + }, + currentTime: this.store.state.currentTime.toString().split('T')[1]?.substring(0, 5), + }; + } + + groupDaysBy({ days, unit, fillMissingDays = true }: Omit, 'weekStartsOn'>) { + return groupDaysBy({ days, unit, fillMissingDays, weekStartsOn: this.options.weekStartsOn } as GroupDaysByProps); + } +} diff --git a/packages/time/src/index.ts b/packages/time/src/index.ts index 6370bcc..0e2559b 100644 --- a/packages/time/src/index.ts +++ b/packages/time/src/index.ts @@ -2,4 +2,4 @@ * TanStack Time */ export * from './utils'; -export * from './calendar'; +export * from './calendar-core'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7de84db..8e864d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,6 +194,9 @@ importers: '@js-temporal/polyfill': specifier: ^0.4.4 version: 0.4.4 + '@tanstack/store': + specifier: ^0.4.1 + version: 0.4.1 devDependencies: csstype: specifier: ^3.1.3 @@ -3310,6 +3313,10 @@ packages: - vite dev: true + /@tanstack/store@0.4.1: + resolution: {integrity: sha512-NvW3MomYSTzQK61AWdtWNIhWgszXFZDRgCNlvSDw/DBaoLqJIlZ0/gKLsditA8un/BGU1NR06+j0a/UNLgXA+Q==} + dev: false + /@testing-library/dom@9.3.4: resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} engines: {node: '>=14'} From 3c48a5e5972a8dedf48556bc145a870ec743ed6e Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Fri, 14 Jun 2024 00:10:37 +0200 Subject: [PATCH 063/128] feat: calendar core --- packages/time/src/calendar-core.ts | 61 +++--- .../src/calendar/getCurrentTimeMarkerProps.ts | 17 -- packages/time/src/calendar/types.ts | 8 +- packages/time/src/tests/calendar-core.test.ts | 188 ++++++++++++++++++ 4 files changed, 225 insertions(+), 49 deletions(-) delete mode 100644 packages/time/src/calendar/getCurrentTimeMarkerProps.ts create mode 100644 packages/time/src/tests/calendar-core.test.ts diff --git a/packages/time/src/calendar-core.ts b/packages/time/src/calendar-core.ts index f5ff903..4f2ed91 100644 --- a/packages/time/src/calendar-core.ts +++ b/packages/time/src/calendar-core.ts @@ -5,38 +5,43 @@ import { generateDateRange } from './calendar/generateDateRange'; import { splitMultiDayEvents } from './calendar/splitMultiDayEvents'; import { getEventProps } from './calendar/getEventProps'; import { groupDaysBy } from './calendar/groupDaysBy'; -import type { GroupDaysByProps} from './calendar/groupDaysBy'; +import type { Properties as CSSProperties } from 'csstype'; +import type { GroupDaysByProps } from './calendar/groupDaysBy'; +import type { CalendarState, Day, Event } from './calendar/types'; -export interface Event { - id: string - startDate: Temporal.PlainDateTime - endDate: Temporal.PlainDateTime - title: string -} - -export interface CalendarState { - currentPeriod: Temporal.PlainDate - viewMode: { - value: number - unit: 'month' | 'week' | 'day' - } - currentTime: Temporal.PlainDateTime -} +export type { CalendarState, Event, Day } from './calendar/types'; -export type Day = { - date: Temporal.PlainDate - events: TEvent[] - isToday: boolean - isInCurrentPeriod: boolean +export interface ViewMode { + value: number; + unit: 'month' | 'week' | 'day'; } export interface CalendarCoreOptions { - weekStartsOn: number; + weekStartsOn?: number; events?: TEvent[]; viewMode: CalendarState['viewMode']; locale?: Parameters['0']; } +export interface CalendarApi { + currentPeriod: CalendarState['currentPeriod']; + viewMode: CalendarState['viewMode']; + currentTime: CalendarState['currentTime']; + days: Array>; + daysNames: string[]; + goToPreviousPeriod: () => void; + goToNextPeriod: () => void; + goToCurrentPeriod: () => void; + goToSpecificPeriod: (date: Temporal.PlainDate) => void; + changeViewMode: (newViewMode: CalendarState['viewMode']) => void; + getEventProps: (id: Event['id']) => { style: CSSProperties } | null; + getCurrentTimeMarkerProps: () => { + style: CSSProperties; + currentTime: string | undefined; + }; + groupDaysBy: (props: Omit, 'weekStartsOn'>) => (Day | null)[][]; +} + export class CalendarCore { store: Store; options: CalendarCoreOptions; @@ -57,14 +62,14 @@ export class CalendarCore { } private getFirstDayOfWeek() { - return getFirstDayOfWeek(this.store.state.currentPeriod.toString(), this.options.weekStartsOn || 1); + return getFirstDayOfWeek(this.store.state.currentPeriod.toString(), this.options.weekStartsOn ?? 1); } private getCalendarDays() { const start = this.store.state.viewMode.unit === 'month' ? this.getFirstDayOfMonth().subtract({ - days: (this.getFirstDayOfMonth().dayOfWeek - (this.options.weekStartsOn || 1) + 7) % 7, + days: (this.getFirstDayOfMonth().dayOfWeek - (this.options.weekStartsOn ?? 1) + 7) % 7, }) : this.store.state.currentPeriod; @@ -75,7 +80,7 @@ export class CalendarCore { .add({ months: this.store.state.viewMode.value }) .subtract({ days: 1 }); const lastDayOfMonthWeekDay = - (lastDayOfMonth.dayOfWeek - (this.options.weekStartsOn || 1) + 7) % 7; + (lastDayOfMonth.dayOfWeek - (this.options.weekStartsOn ?? 1) + 7) % 7; end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }); break; } @@ -155,7 +160,7 @@ export class CalendarCore { const baseDate = Temporal.PlainDate.from('2024-01-01'); return Array.from({ length: 7 }).map((_, i) => baseDate - .add({ days: (i + (this.options.weekStartsOn || 1) - 1) % 7 }) + .add({ days: (i + (this.options.weekStartsOn ?? 1) - 1) % 7 }) .toLocaleString(this.options.locale, { weekday: 'short' }), ); } @@ -260,7 +265,7 @@ export class CalendarCore { return getEventProps(this.getEventMap(), id, this.store.state); } - getCurrentTimeMarkerProps() { + getCurrentTimeMarkerProps(): { style: CSSProperties; currentTime: string | undefined } { const { hour, minute } = this.store.state.currentTime; const currentTimeInMinutes = hour * 60 + minute; const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100; @@ -276,6 +281,6 @@ export class CalendarCore { } groupDaysBy({ days, unit, fillMissingDays = true }: Omit, 'weekStartsOn'>) { - return groupDaysBy({ days, unit, fillMissingDays, weekStartsOn: this.options.weekStartsOn } as GroupDaysByProps); + return groupDaysBy({ days, unit, fillMissingDays, weekStartsOn: this.options.weekStartsOn ?? 1 } as GroupDaysByProps); } } diff --git a/packages/time/src/calendar/getCurrentTimeMarkerProps.ts b/packages/time/src/calendar/getCurrentTimeMarkerProps.ts deleted file mode 100644 index 76d17c9..0000000 --- a/packages/time/src/calendar/getCurrentTimeMarkerProps.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Temporal } from "@js-temporal/polyfill"; -import type { Properties as CSSProperties } from "csstype"; - -export const getCurrentTimeMarkerProps = (currentTime: Temporal.PlainDateTime): { style: CSSProperties; currentTime: string | undefined } => { - const { hour, minute } = currentTime; - const currentTimeInMinutes = hour * 60 + minute; - const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100; - - return { - style: { - position: 'absolute', - top: `${percentageOfDay}%`, - left: 0, - }, - currentTime: currentTime.toString().split('T')[1]?.substring(0, 5), - }; -}; \ No newline at end of file diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index d8552b4..8da9ad3 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -1,10 +1,10 @@ import type { Temporal } from "@js-temporal/polyfill" export interface Event { - id: string - startDate: Temporal.PlainDateTime - endDate: Temporal.PlainDateTime - title: string + id: string; + startDate: Temporal.PlainDateTime; + endDate: Temporal.PlainDateTime; + title: string; } export interface CalendarState { diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts new file mode 100644 index 0000000..25c576c --- /dev/null +++ b/packages/time/src/tests/calendar-core.test.ts @@ -0,0 +1,188 @@ +import { Temporal } from '@js-temporal/polyfill'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { CalendarCore } from '../calendar-core'; +import type { CalendarCoreOptions, Event } from '../calendar-core'; + +describe('CalendarCore', () => { + let options: CalendarCoreOptions; + let calendarCore: CalendarCore; + const mockDate = Temporal.PlainDate.from('2023-06-15'); + const mockDateTime = Temporal.PlainDateTime.from('2023-06-15T10:00'); + + beforeEach(() => { + options = { + weekStartsOn: 1, + viewMode: { value: 1, unit: 'month' }, + events: [ + { + id: '1', + startDate: Temporal.PlainDateTime.from('2023-06-10T09:00'), + endDate: Temporal.PlainDateTime.from('2023-06-10T10:00'), + title: 'Event 1', + }, + { + id: '2', + startDate: Temporal.PlainDateTime.from('2023-06-12T11:00'), + endDate: Temporal.PlainDateTime.from('2023-06-12T12:00'), + title: 'Event 2', + }, + ], + }; + calendarCore = new CalendarCore(options); + vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate); + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(mockDateTime); + }); + + test('should initialize with the correct current period', () => { + const today = Temporal.Now.plainDateISO(); + expect(calendarCore.store.state.currentPeriod).toEqual(today); + }); + + test('should get the correct days with events for the month', () => { + const daysWithEvents = calendarCore.getDaysWithEvents(); + expect(daysWithEvents.length).toBeGreaterThan(0); + }); + + test('should correctly map events to days', () => { + const daysWithEvents = calendarCore.getDaysWithEvents(); + const dayWithEvent1 = daysWithEvents.find((day) => day.date.equals(Temporal.PlainDate.from('2023-06-10'))); + const dayWithEvent2 = daysWithEvents.find((day) => day.date.equals(Temporal.PlainDate.from('2023-06-12'))); + expect(dayWithEvent1?.events).toHaveLength(1); + expect(dayWithEvent1?.events[0]?.id).toBe('1'); + expect(dayWithEvent2?.events).toHaveLength(1); + expect(dayWithEvent2?.events[0]?.id).toBe('2'); + }); + + test('should change view mode correctly', () => { + calendarCore.changeViewMode({ value: 2, unit: 'week' }); + expect(calendarCore.store.state.viewMode.value).toBe(2); + expect(calendarCore.store.state.viewMode.unit).toBe('week'); + }); + + test('should go to previous period correctly', () => { + const initialPeriod = calendarCore.store.state.currentPeriod; + calendarCore.goToPreviousPeriod(); + const expectedPreviousMonth = initialPeriod.subtract({ months: 1 }); + expect(calendarCore.store.state.currentPeriod).toEqual(expectedPreviousMonth); + }); + + test('should go to next period correctly', () => { + const initialPeriod = calendarCore.store.state.currentPeriod; + calendarCore.goToNextPeriod(); + const expectedNextMonth = initialPeriod.add({ months: 1 }); + expect(calendarCore.store.state.currentPeriod).toEqual(expectedNextMonth); + }); + + test('should go to current period correctly', () => { + calendarCore.goToNextPeriod(); + calendarCore.goToCurrentPeriod(); + const today = Temporal.Now.plainDateISO(); + expect(calendarCore.store.state.currentPeriod).toEqual(today); + }); + + test('should go to specific period correctly', () => { + const specificDate = Temporal.PlainDate.from('2023-07-01'); + calendarCore.goToSpecificPeriod(specificDate); + expect(calendarCore.store.state.currentPeriod).toEqual(specificDate); + }); + + test('should update current time correctly', () => { + const initialTime = calendarCore.store.state.currentTime; + const newMockDateTime = initialTime.add({ minutes: 1 }); + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(newMockDateTime); + + calendarCore.updateCurrentTime(); + const updatedTime = calendarCore.store.state.currentTime; + expect(updatedTime).toEqual(newMockDateTime); + }); + + test('should return the correct props for the current time marker', () => { + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:00:00')); + + const coreOptions: CalendarCoreOptions = { + weekStartsOn: 1, + viewMode: { value: 1, unit: 'week' }, + events: [], + }; + calendarCore = new CalendarCore(coreOptions); + + const currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps(); + + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.83333333333333%', + left: 0, + }, + currentTime: '11:00', + }); + }); + + test('should update the current time marker props after time passes', () => { + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:00:00')); + + const coreOptions: CalendarCoreOptions = { + weekStartsOn: 1, + viewMode: { value: 1, unit: 'week' }, + events: [], + }; + calendarCore = new CalendarCore(coreOptions); + + let currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps(); + + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.83333333333333%', + left: 0, + }, + currentTime: '11:00', + }); + + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:01:00')); + + calendarCore.updateCurrentTime(); + currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps(); + + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.90277777777778%', + left: 0, + }, + currentTime: '11:01', + }); + }); + + test('should update the current time marker props after time passes when the next minute is in less than a minute', () => { + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:00:55')); + + + const coreOptions: CalendarCoreOptions = { + weekStartsOn: 1, + viewMode: { value: 1, unit: 'week' }, + events: [], + }; + calendarCore = new CalendarCore(coreOptions); + + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:01:00')); + + calendarCore.updateCurrentTime(); + const currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps(); + + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.90277777777778%', + left: 0, + }, + currentTime: '11:01', + }); + }); + + test('should group days correctly', () => { + const daysWithEvents = calendarCore.getDaysWithEvents(); + const groupedDays = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'month'}); + expect(groupedDays.length).toBeGreaterThan(0); + }); +}); From 649947544969204389342087fa119f36f8233be9 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Fri, 14 Jun 2024 00:10:44 +0200 Subject: [PATCH 064/128] feat: calendar core --- packages/react-time/package.json | 1 + .../react-time/src/tests/useCalendar.test.tsx | 26 ------ .../src/useCalendar/calendarActions.ts | 13 --- .../react-time/src/useCalendar/useCalendar.ts | 93 +++++++++---------- .../src/useCalendar/useCalendarReducer.ts | 90 ------------------ .../src/useCalendar/useCalendarState.ts | 17 ---- pnpm-lock.yaml | 15 +++ 7 files changed, 62 insertions(+), 193 deletions(-) delete mode 100644 packages/react-time/src/useCalendar/calendarActions.ts delete mode 100644 packages/react-time/src/useCalendar/useCalendarReducer.ts delete mode 100644 packages/react-time/src/useCalendar/useCalendarState.ts diff --git a/packages/react-time/package.json b/packages/react-time/package.json index 0181e8d..84a9a61 100644 --- a/packages/react-time/package.json +++ b/packages/react-time/package.json @@ -63,6 +63,7 @@ }, "dependencies": { "@js-temporal/polyfill": "^0.4.4", + "@tanstack/react-store": "^0.4.1", "@tanstack/time": "workspace:*", "typesafe-actions": "^5.1.0", "use-sync-external-store": "^1.2.0" diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 7bfe44b..c59201b 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -2,8 +2,6 @@ import { Temporal } from '@js-temporal/polyfill' import { describe, expect, test, vi } from 'vitest' import { act, renderHook, waitFor } from '@testing-library/react' import { useCalendar } from '../useCalendar' -import type { UseCalendarAction } from '../useCalendar/calendarActions'; -import type { UseCalendarState } from '../useCalendar/useCalendarState'; describe('useCalendar', () => { const events = [ @@ -332,30 +330,6 @@ describe('useCalendar', () => { ) }) - test(`should allow overriding the reducer`, () => { - const customReducer = (state: UseCalendarState, action: UseCalendarAction) => { - if (action.type === 'SET_NEXT_PERIOD') { - return { - ...state, - currentPeriod: state.currentPeriod.add({ months: 2 }), - } - } - - return state - } - - const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, reducer: customReducer }), - ) - - act(() => { - result.current.goToNextPeriod() - }) - - const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 2 }) - expect(result.current.currentPeriod).toEqual(expectedNextMonth) - }); - test('should group days by months correctly', () => { const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 2, unit: 'month' }, locale: 'en-US' }) diff --git a/packages/react-time/src/useCalendar/calendarActions.ts b/packages/react-time/src/useCalendar/calendarActions.ts deleted file mode 100644 index abf5c73..0000000 --- a/packages/react-time/src/useCalendar/calendarActions.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createAction } from 'typesafe-actions'; -import type { Temporal } from '@js-temporal/polyfill'; -import type { ActionType } from 'typesafe-actions'; -import type { UseCalendarState } from './useCalendarState'; - -const setViewMode = createAction('SET_VIEW_MODE')(); -const updateCurrentTime = createAction('UPDATE_CURRENT_TIME')(); -const setCurrentPeriod = createAction('SET_CURRENT_PERIOD')(); -const goToNextPeriod = createAction('SET_NEXT_PERIOD')<{ weekStartsOn: number }>(); -const goToPreviousPeriod = createAction('SET_PREVIOUS_PERIOD')<{ weekStartsOn: number }>(); - -export const actions = { setCurrentPeriod, setViewMode, updateCurrentTime, goToNextPeriod, goToPreviousPeriod }; -export type UseCalendarAction = ActionType; diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 5d8acf3..f6b168c 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,82 +1,81 @@ -import { useEffect, useRef, useState, useTransition } from 'react'; -import { Temporal } from '@js-temporal/polyfill'; +import { useEffect, useRef, useState, useTransition } from 'react' +import { useStore } from '@tanstack/react-store' +import { Temporal } from '@js-temporal/polyfill' +import { CalendarCore, type CalendarState, type Event } from '@tanstack/time' +import type { CalendarApi, CalendarCoreOptions } from '@tanstack/time' -import { CalendarCore, type CalendarCoreOptions, type Event } from '@tanstack/time'; -import type { CalendarState} from '@tanstack/time'; +export const useCalendar = ( + options: CalendarCoreOptions, +): CalendarApi & { isPending: boolean } => { + const [calendarCore] = useState(() => new CalendarCore(options)) + const state = useStore(calendarCore.store) -export const useCalendar = (options: CalendarCoreOptions) => { - const [calendarCore] = useState(() => new CalendarCore(options)); - const [state, setState] = useState(calendarCore.store.state); - - const [isPending, startTransition] = useTransition(); - const currentTimeInterval = useRef(); + const [isPending, startTransition] = useTransition() + const currentTimeInterval = useRef() useEffect(() => { const updateCurrentTime = () => { - calendarCore.updateCurrentTime(); - setState({ ...calendarCore.store.state }); - }; + calendarCore.updateCurrentTime() + } - if (currentTimeInterval.current) clearTimeout(currentTimeInterval.current); + if (currentTimeInterval.current) clearTimeout(currentTimeInterval.current) - const now = Temporal.Now.plainDateTimeISO(); - const msToNextMinute = (60 - now.second) * 1000 - now.millisecond; + const now = Temporal.Now.plainDateTimeISO() + const msToNextMinute = (60 - now.second) * 1000 - now.millisecond currentTimeInterval.current = setTimeout(() => { - updateCurrentTime(); - currentTimeInterval.current = setInterval(updateCurrentTime, 60000); - }, msToNextMinute); + updateCurrentTime() + currentTimeInterval.current = setInterval(updateCurrentTime, 60000) + }, msToNextMinute) - return () => clearTimeout(currentTimeInterval.current); - }, [calendarCore]); + return () => clearTimeout(currentTimeInterval.current) + }, [calendarCore]) const goToPreviousPeriod = () => { startTransition(() => { - calendarCore.goToPreviousPeriod(); - setState({ ...calendarCore.store.state }); - }); - }; + calendarCore.goToPreviousPeriod() + }) + } const goToNextPeriod = () => { startTransition(() => { - calendarCore.goToNextPeriod(); - setState({ ...calendarCore.store.state }); - }); - }; + calendarCore.goToNextPeriod() + }) + } const goToCurrentPeriod = () => { startTransition(() => { - calendarCore.goToCurrentPeriod(); - setState({ ...calendarCore.store.state }); - }); - }; + calendarCore.goToCurrentPeriod() + }) + } const goToSpecificPeriod = (date: Temporal.PlainDate) => { startTransition(() => { - calendarCore.goToSpecificPeriod(date); - setState({ ...calendarCore.store.state }); - }); - }; + calendarCore.goToSpecificPeriod(date) + }) + } const changeViewMode = (newViewMode: CalendarState['viewMode']) => { startTransition(() => { - calendarCore.changeViewMode(newViewMode); - setState({ ...calendarCore.store.state }); - }); - }; + calendarCore.changeViewMode(newViewMode) + }) + } return { - ...state, + currentPeriod: state.currentPeriod, + viewMode: state.viewMode, + currentTime: state.currentTime, + days: calendarCore.getDaysWithEvents(), + daysNames: calendarCore.getDaysNames(), goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, goToSpecificPeriod, - days: calendarCore.getDaysWithEvents(), - daysNames: calendarCore.getDaysNames(), changeViewMode, getEventProps: calendarCore.getEventProps.bind(calendarCore), - getCurrentTimeMarkerProps: calendarCore.getCurrentTimeMarkerProps.bind(calendarCore), + getCurrentTimeMarkerProps: + calendarCore.getCurrentTimeMarkerProps.bind(calendarCore), isPending, groupDaysBy: calendarCore.groupDaysBy.bind(calendarCore), - }; -}; + } +} diff --git a/packages/react-time/src/useCalendar/useCalendarReducer.ts b/packages/react-time/src/useCalendar/useCalendarReducer.ts deleted file mode 100644 index d3b87f5..0000000 --- a/packages/react-time/src/useCalendar/useCalendarReducer.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { useMemo, useReducer } from 'react' -import { createReducer } from 'typesafe-actions' - -import { getFirstDayOfMonth, getFirstDayOfWeek } from '@tanstack/time' -import { type UseCalendarAction, actions } from './calendarActions' -import type { UseCalendarState } from './useCalendarState' - -const createCalendarReducer = (initialState: UseCalendarState) => { - return createReducer(initialState) - .handleAction(actions.setCurrentPeriod, (state, action) => ({ - ...state, - currentPeriod: action.payload, - })) - .handleAction(actions.setViewMode, (state, action) => ({ - ...state, - viewMode: action.payload, - })) - .handleAction(actions.updateCurrentTime, (state, action) => ({ - ...state, - currentTime: action.payload, - })) - .handleAction(actions.goToPreviousPeriod, (state, action) => { - const firstDayOfMonth = getFirstDayOfMonth(state.currentPeriod.toString({ calendarName: 'auto' }).substring(0, 7)); - const firstDayOfWeek = getFirstDayOfWeek(state.currentPeriod.toString(), action.payload.weekStartsOn); - - switch (state.viewMode.unit) { - case 'month': { - const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: state.viewMode.value }); - return { - ...state, - currentPeriod: firstDayOfPrevMonth, - }; - } - case 'week': { - const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: state.viewMode.value }); - return { - ...state, - currentPeriod: firstDayOfPrevWeek, - }; - } - case 'day': { - const prevCustomStart = state.currentPeriod.subtract({ days: state.viewMode.value }); - return { - ...state, - currentPeriod: prevCustomStart, - }; - } - default: - return state; - } - }) - .handleAction(actions.goToNextPeriod, (state, action) => { - const firstDayOfMonth = getFirstDayOfMonth(state.currentPeriod.toString({ calendarName: 'auto' }).substring(0, 7)); - const firstDayOfWeek = getFirstDayOfWeek(state.currentPeriod.toString(), action.payload.weekStartsOn); - - switch (state.viewMode.unit) { - case 'month': { - const firstDayOfNextMonth = firstDayOfMonth.add({ months: state.viewMode.value }); - return { - ...state, - currentPeriod: firstDayOfNextMonth, - }; - } - case 'week': { - const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: state.viewMode.value }); - return { - ...state, - currentPeriod: firstDayOfNextWeek, - }; - } - case 'day': { - const nextCustomStart = state.currentPeriod.add({ days: state.viewMode.value }); - return { - ...state, - currentPeriod: nextCustomStart, - }; - } - default: - return state; - } - }); -}; - -export const useCalendarReducer = ( - initialState: TState, - extReducer?: (state: TState, action: UseCalendarAction) => TState, -) => { - const reducer = useMemo(() => extReducer ?? createCalendarReducer(initialState), [extReducer, initialState]) - return useReducer(reducer, initialState) -} diff --git a/packages/react-time/src/useCalendar/useCalendarState.ts b/packages/react-time/src/useCalendar/useCalendarState.ts deleted file mode 100644 index c5a3b10..0000000 --- a/packages/react-time/src/useCalendar/useCalendarState.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Temporal } from '@js-temporal/polyfill' - -export interface Event { - id: string - startDate: Temporal.PlainDateTime - endDate: Temporal.PlainDateTime - title: string -} - -export interface UseCalendarState { - currentPeriod: Temporal.PlainDate - viewMode: { - value: number - unit: 'month' | 'week' | 'day' - } - currentTime: Temporal.PlainDateTime -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e864d4..626d76f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -153,6 +153,9 @@ importers: '@js-temporal/polyfill': specifier: ^0.4.4 version: 0.4.4 + '@tanstack/react-store': + specifier: ^0.4.1 + version: 0.4.1(react-dom@18.2.0)(react@18.2.0) '@tanstack/time': specifier: workspace:* version: link:../time @@ -3313,6 +3316,18 @@ packages: - vite dev: true + /@tanstack/react-store@0.4.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cyriofh2I6dPOPJf2W0K+ON5q08ezevLTUhC1txiUnrJ9XFFFPr0X8CmGnXzucI2c0t0V6wYZc0GCz4zOAeptg==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + dependencies: + '@tanstack/store': 0.4.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /@tanstack/store@0.4.1: resolution: {integrity: sha512-NvW3MomYSTzQK61AWdtWNIhWgszXFZDRgCNlvSDw/DBaoLqJIlZ0/gKLsditA8un/BGU1NR06+j0a/UNLgXA+Q==} dev: false From 2ac6065adaa2aa491ebee0504c3cdac0535e338f Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Fri, 14 Jun 2024 00:17:58 +0200 Subject: [PATCH 065/128] docs: calendar core --- docs/reference/calendar-core.md | 113 ++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 docs/reference/calendar-core.md diff --git a/docs/reference/calendar-core.md b/docs/reference/calendar-core.md new file mode 100644 index 0000000..8c7b244 --- /dev/null +++ b/docs/reference/calendar-core.md @@ -0,0 +1,113 @@ +--- +title: Calendar Core +id: calendar-core +--- + +### `CalendarCore` + +```tsx +export class CalendarCore { + constructor(options: CalendarCoreOptions); +} +``` + +The `CalendarCore` class provides a set of functionalities for managing calendar events, view modes, and period navigation. This class is designed to be used in various calendar applications where precise date management and event handling are required. + + +#### Parameters + +- `weekStartsOn?: number` + - An optional number that specifies the day of the week that the calendar should start on. It defaults to 1 (Monday). +- `events?: TEvent[]` + - An optional array of events that the calendar should display. +- `viewMode: ViewMode` + - An object that specifies the initial view mode of the calendar. +- `locale?: Parameters['0']` + - An optional string that specifies the locale to use for formatting dates and times. + + +#### Returns + +- `getDaysWithEvents(): Array>` + - Returns an array of days in the current period with their associated events. +- `getDaysNames(): string[]` + - Returns an array of strings representing the names of the days of the week based on the locale and week start day. +- `changeViewMode(newViewMode: ViewMode): void` + - Changes the view mode of the calendar. +- `goToPreviousPeriod(): void` + - Navigates to the previous period based on the current view mode. +- `goToNextPeriod(): void` + - Navigates to the next period based on the current view mode. +- `goToCurrentPeriod(): void` + - Navigates to the current period. +- `goToSpecificPeriod(date: Temporal.PlainDate): void` + - Navigates to a specific period based on the provided date. +- `updateCurrentTime(): void` + - Updates the current time. +- `getEventProps(id: Event['id']): { style: CSSProperties } | null` + - Retrieves the style properties for a specific event based on its ID. +- `getCurrentTimeMarkerProps(): { style: CSSProperties; currentTime: string | undefined }` + - Retrieves the style properties and current time for the current time marker. +- `groupDaysBy(props: Omit, 'weekStartsOn'>): (Day | null)[][]` + - Groups the days in the current period by a specified unit. The fillMissingDays parameter can be used to fill in missing days with previous or next month's days. + + +#### Example Usage + +```ts +import { CalendarCore, Event } from 'your-calendar-core-package'; +import { Temporal } from '@js-temporal/polyfill'; + +interface MyEvent extends Event { + location: string; +} + +const events: MyEvent[] = [ + { + id: '1', + startDate: Temporal.PlainDateTime.from('2024-06-10T09:00'), + endDate: Temporal.PlainDateTime.from('2024-06-10T10:00'), + title: 'Event 1', + location: 'Room 101', + }, + { + id: '2', + startDate: Temporal.PlainDateTime.from('2024-06-12T11:00'), + endDate: Temporal.PlainDateTime.from('2024-06-12T12:00'), + title: 'Event 2', + location: 'Room 202', + }, +]; + +const calendarCore = new CalendarCore({ + weekStartsOn: 1, + viewMode: { value: 1, unit: 'month' }, + events, + locale: 'en-US', +}); + +// Get days with events +const daysWithEvents = calendarCore.getDaysWithEvents(); +console.log(daysWithEvents); + +// Change view mode to week +calendarCore.changeViewMode({ value: 1, unit: 'week' }); + +// Navigate to the next period +calendarCore.goToNextPeriod(); + +// Update current time +calendarCore.updateCurrentTime(); + +// Get event properties +const eventProps = calendarCore.getEventProps('1'); +console.log(eventProps); + +// Get current time marker properties +const currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps(); +console.log(currentTimeMarkerProps); + +// Group days by week +const groupedDays = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'week' }); +console.log(groupedDays); +``` From a71f5a83ae582053a8b97de11aab67f2fbfc06b3 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Fri, 14 Jun 2024 00:23:15 +0200 Subject: [PATCH 066/128] refactor: useCalendar --- .../react-time/src/tests/useCalendar.test.tsx | 21 ++++++++++--------- .../react-time/src/useCalendar/useCalendar.ts | 13 ++++++------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index c59201b..ea50fed 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -207,21 +207,22 @@ describe('useCalendar', () => { vi.setSystemTime(new Date('2024-06-01T11:00:55')); const { result } = renderHook(() => useCalendar({ viewMode: { value: 1, unit: 'week' } })); + const currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); act(() => { vi.advanceTimersByTime(5000); }) - const currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); - - expect(currentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.90277777777778%', - left: 0, - }, - currentTime: '11:01', - }); + waitFor(() => { + expect(currentTimeMarkerProps).toEqual({ + style: { + position: 'absolute', + top: '45.90277777777778%', + left: 0, + }, + currentTime: '11:01', + }); + }) }); test('should render array of days', () => { diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index f6b168c..6cbcdbf 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, useTransition } from 'react' +import { useCallback, useEffect, useRef, useState, useTransition } from 'react' import { useStore } from '@tanstack/react-store' import { Temporal } from '@js-temporal/polyfill' import { CalendarCore, type CalendarState, type Event } from '@tanstack/time' @@ -9,15 +9,14 @@ export const useCalendar = ( ): CalendarApi & { isPending: boolean } => { const [calendarCore] = useState(() => new CalendarCore(options)) const state = useStore(calendarCore.store) - const [isPending, startTransition] = useTransition() const currentTimeInterval = useRef() - useEffect(() => { - const updateCurrentTime = () => { - calendarCore.updateCurrentTime() - } + const updateCurrentTime = useCallback(() => { + calendarCore.updateCurrentTime() + }, [calendarCore]) + useEffect(() => { if (currentTimeInterval.current) clearTimeout(currentTimeInterval.current) const now = Temporal.Now.plainDateTimeISO() @@ -29,7 +28,7 @@ export const useCalendar = ( }, msToNextMinute) return () => clearTimeout(currentTimeInterval.current) - }, [calendarCore]) + }, [calendarCore, updateCurrentTime]) const goToPreviousPeriod = () => { startTransition(() => { From 965de147280b039abb4147478ed9d7580e88c6c5 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 17 Jun 2024 16:45:53 +0200 Subject: [PATCH 067/128] test: calendar core --- packages/time/src/tests/calendar-core.test.ts | 67 ++----------------- 1 file changed, 7 insertions(+), 60 deletions(-) diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 25c576c..74e6e34 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -118,67 +118,14 @@ describe('CalendarCore', () => { }); }); - test('should update the current time marker props after time passes', () => { - vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:00:00')); - - const coreOptions: CalendarCoreOptions = { - weekStartsOn: 1, - viewMode: { value: 1, unit: 'week' }, - events: [], - }; - calendarCore = new CalendarCore(coreOptions); - - let currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps(); - - expect(currentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.83333333333333%', - left: 0, - }, - currentTime: '11:00', - }); - - vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:01:00')); - - calendarCore.updateCurrentTime(); - currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps(); - - expect(currentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.90277777777778%', - left: 0, - }, - currentTime: '11:01', - }); - }); - - test('should update the current time marker props after time passes when the next minute is in less than a minute', () => { - vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:00:55')); - - - const coreOptions: CalendarCoreOptions = { - weekStartsOn: 1, - viewMode: { value: 1, unit: 'week' }, - events: [], - }; - calendarCore = new CalendarCore(coreOptions); - - vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:01:00')); - + test('should update the current time on', () => { + const initialTime = calendarCore.store.state.currentTime; + const newMockDateTime = initialTime.add({ minutes: 1 }); + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(newMockDateTime); + calendarCore.updateCurrentTime(); - const currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps(); - - expect(currentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.90277777777778%', - left: 0, - }, - currentTime: '11:01', - }); - }); + expect(initialTime).toEqual(newMockDateTime); + }) test('should group days correctly', () => { const daysWithEvents = calendarCore.getDaysWithEvents(); From 301d4b891bd04b99d953c6077cc288e4df5f70c5 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 17 Jun 2024 18:22:43 +0200 Subject: [PATCH 068/128] refactor: useCalendar --- .../react-time/src/useCalendar/useCalendar.ts | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 6cbcdbf..1c87c74 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState, useTransition } from 'react' import { useStore } from '@tanstack/react-store' import { Temporal } from '@js-temporal/polyfill' import { CalendarCore, type CalendarState, type Event } from '@tanstack/time' -import type { CalendarApi, CalendarCoreOptions } from '@tanstack/time' +import type { CalendarApi, CalendarCoreOptions, GroupDaysByProps } from '@tanstack/time' export const useCalendar = ( options: CalendarCoreOptions, @@ -30,35 +30,41 @@ export const useCalendar = ( return () => clearTimeout(currentTimeInterval.current) }, [calendarCore, updateCurrentTime]) - const goToPreviousPeriod = () => { + const goToPreviousPeriod = useCallback(() => { startTransition(() => { calendarCore.goToPreviousPeriod() }) - } + }, [calendarCore, startTransition]) - const goToNextPeriod = () => { + const goToNextPeriod = useCallback(() => { startTransition(() => { calendarCore.goToNextPeriod() }) - } + }, [calendarCore, startTransition]) - const goToCurrentPeriod = () => { + const goToCurrentPeriod = useCallback(() => { startTransition(() => { calendarCore.goToCurrentPeriod() }) - } + }, [calendarCore, startTransition]) - const goToSpecificPeriod = (date: Temporal.PlainDate) => { + const goToSpecificPeriod = useCallback((date: Temporal.PlainDate) => { startTransition(() => { calendarCore.goToSpecificPeriod(date) }) - } + }, [calendarCore, startTransition]) - const changeViewMode = (newViewMode: CalendarState['viewMode']) => { + const changeViewMode = useCallback((newViewMode: CalendarState['viewMode']) => { startTransition(() => { calendarCore.changeViewMode(newViewMode) }) - } + }, [calendarCore, startTransition]) + + const getEventProps = useCallback((id: TEvent['id']) => calendarCore.getEventProps(id), [calendarCore]) + + const getCurrentTimeMarkerProps = useCallback(() => calendarCore.getCurrentTimeMarkerProps(), [calendarCore]) + + const groupDaysBy = useCallback((props: Omit, 'weekStartsOn'>) => calendarCore.groupDaysBy(props), [calendarCore]) return { currentPeriod: state.currentPeriod, @@ -71,10 +77,9 @@ export const useCalendar = ( goToCurrentPeriod, goToSpecificPeriod, changeViewMode, - getEventProps: calendarCore.getEventProps.bind(calendarCore), - getCurrentTimeMarkerProps: - calendarCore.getCurrentTimeMarkerProps.bind(calendarCore), + getEventProps, + getCurrentTimeMarkerProps, isPending, - groupDaysBy: calendarCore.groupDaysBy.bind(calendarCore), + groupDaysBy, } } From 1324311582943a61a57e8e56bf497cefeff00eda Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 17 Jun 2024 18:35:35 +0200 Subject: [PATCH 069/128] refactor: useCalendar --- packages/react-time/src/useCalendar/useCalendar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 1c87c74..ed0f428 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -64,7 +64,7 @@ export const useCalendar = ( const getCurrentTimeMarkerProps = useCallback(() => calendarCore.getCurrentTimeMarkerProps(), [calendarCore]) - const groupDaysBy = useCallback((props: Omit, 'weekStartsOn'>) => calendarCore.groupDaysBy(props), [calendarCore]) + const groupDaysBy = useCallback((props: Omit, "weekStartsOn">) => calendarCore.groupDaysBy(props), [calendarCore]) return { currentPeriod: state.currentPeriod, From 042bb13e11eef8d891edfeaf486f373c344e14b3 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 17 Jun 2024 18:35:42 +0200 Subject: [PATCH 070/128] refactor: useCalendar --- packages/time/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/time/src/index.ts b/packages/time/src/index.ts index 0e2559b..e2e01d1 100644 --- a/packages/time/src/index.ts +++ b/packages/time/src/index.ts @@ -3,3 +3,4 @@ */ export * from './utils'; export * from './calendar-core'; +export * from './calendar'; From 82c1c8776d4c076e4bb28cbcd49af0f9eec95914 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 17 Jun 2024 22:28:19 +0200 Subject: [PATCH 071/128] refactor(useCalendar): types --- .../react-time/src/useCalendar/useCalendar.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index ed0f428..6dd3412 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,8 +1,8 @@ import { useCallback, useEffect, useRef, useState, useTransition } from 'react' import { useStore } from '@tanstack/react-store' import { Temporal } from '@js-temporal/polyfill' -import { CalendarCore, type CalendarState, type Event } from '@tanstack/time' -import type { CalendarApi, CalendarCoreOptions, GroupDaysByProps } from '@tanstack/time' +import { CalendarCore, type Event } from '@tanstack/time' +import type { CalendarApi, CalendarCoreOptions } from '@tanstack/time' export const useCalendar = ( options: CalendarCoreOptions, @@ -12,7 +12,7 @@ export const useCalendar = ( const [isPending, startTransition] = useTransition() const currentTimeInterval = useRef() - const updateCurrentTime = useCallback(() => { + const updateCurrentTime = useCallback(() => { calendarCore.updateCurrentTime() }, [calendarCore]) @@ -30,41 +30,41 @@ export const useCalendar = ( return () => clearTimeout(currentTimeInterval.current) }, [calendarCore, updateCurrentTime]) - const goToPreviousPeriod = useCallback(() => { + const goToPreviousPeriod = useCallback(() => { startTransition(() => { calendarCore.goToPreviousPeriod() }) }, [calendarCore, startTransition]) - const goToNextPeriod = useCallback(() => { + const goToNextPeriod = useCallback(() => { startTransition(() => { calendarCore.goToNextPeriod() }) }, [calendarCore, startTransition]) - const goToCurrentPeriod = useCallback(() => { + const goToCurrentPeriod = useCallback(() => { startTransition(() => { calendarCore.goToCurrentPeriod() }) }, [calendarCore, startTransition]) - const goToSpecificPeriod = useCallback((date: Temporal.PlainDate) => { + const goToSpecificPeriod = useCallback((date) => { startTransition(() => { calendarCore.goToSpecificPeriod(date) }) }, [calendarCore, startTransition]) - const changeViewMode = useCallback((newViewMode: CalendarState['viewMode']) => { + const changeViewMode = useCallback((newViewMode) => { startTransition(() => { calendarCore.changeViewMode(newViewMode) }) }, [calendarCore, startTransition]) - const getEventProps = useCallback((id: TEvent['id']) => calendarCore.getEventProps(id), [calendarCore]) + const getEventProps = useCallback((id) => calendarCore.getEventProps(id), [calendarCore]) - const getCurrentTimeMarkerProps = useCallback(() => calendarCore.getCurrentTimeMarkerProps(), [calendarCore]) + const getCurrentTimeMarkerProps = useCallback(() => calendarCore.getCurrentTimeMarkerProps(), [calendarCore]) - const groupDaysBy = useCallback((props: Omit, "weekStartsOn">) => calendarCore.groupDaysBy(props), [calendarCore]) + const groupDaysBy = useCallback((props) => calendarCore.groupDaysBy(props), [calendarCore]) return { currentPeriod: state.currentPeriod, From f61a99d3a9b9f242cb3dab616895e7d8c75d3696 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 20 Jun 2024 23:00:34 +0200 Subject: [PATCH 072/128] refactor: move calendar-related files to the core directory --- .../src/{calendar-core.ts => core/calendar.ts} | 16 ++++++++-------- packages/time/src/core/index.ts | 1 + packages/time/src/index.ts | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) rename packages/time/src/{calendar-core.ts => core/calendar.ts} (94%) create mode 100644 packages/time/src/core/index.ts diff --git a/packages/time/src/calendar-core.ts b/packages/time/src/core/calendar.ts similarity index 94% rename from packages/time/src/calendar-core.ts rename to packages/time/src/core/calendar.ts index 4f2ed91..b5ce5e5 100644 --- a/packages/time/src/calendar-core.ts +++ b/packages/time/src/core/calendar.ts @@ -1,15 +1,15 @@ import { Store } from '@tanstack/store'; import { Temporal } from '@js-temporal/polyfill'; -import { getFirstDayOfMonth, getFirstDayOfWeek } from './utils'; -import { generateDateRange } from './calendar/generateDateRange'; -import { splitMultiDayEvents } from './calendar/splitMultiDayEvents'; -import { getEventProps } from './calendar/getEventProps'; -import { groupDaysBy } from './calendar/groupDaysBy'; +import { getFirstDayOfMonth, getFirstDayOfWeek } from '../utils'; +import { generateDateRange } from '../calendar/generateDateRange'; +import { splitMultiDayEvents } from '../calendar/splitMultiDayEvents'; +import { getEventProps } from '../calendar/getEventProps'; +import { groupDaysBy } from '../calendar/groupDaysBy'; import type { Properties as CSSProperties } from 'csstype'; -import type { GroupDaysByProps } from './calendar/groupDaysBy'; -import type { CalendarState, Day, Event } from './calendar/types'; +import type { GroupDaysByProps } from '../calendar/groupDaysBy'; +import type { CalendarState, Day, Event } from '../calendar/types'; -export type { CalendarState, Event, Day } from './calendar/types'; +export type { CalendarState, Event, Day } from '../calendar/types'; export interface ViewMode { value: number; diff --git a/packages/time/src/core/index.ts b/packages/time/src/core/index.ts new file mode 100644 index 0000000..e00be3c --- /dev/null +++ b/packages/time/src/core/index.ts @@ -0,0 +1 @@ +export * from './calendar' diff --git a/packages/time/src/index.ts b/packages/time/src/index.ts index e2e01d1..0535016 100644 --- a/packages/time/src/index.ts +++ b/packages/time/src/index.ts @@ -2,5 +2,5 @@ * TanStack Time */ export * from './utils'; -export * from './calendar-core'; -export * from './calendar'; +export * from './core'; + From 1465e850ba989600f9630478d2d0779eba5e8bf4 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 20 Jun 2024 23:16:25 +0200 Subject: [PATCH 073/128] refactor: Update CalendarCoreOptions interface in calendar.ts --- packages/time/src/core/calendar.ts | 2 +- packages/time/src/core/date-picker.ts | 64 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 packages/time/src/core/date-picker.ts diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index b5ce5e5..4412972 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -16,7 +16,7 @@ export interface ViewMode { unit: 'month' | 'week' | 'day'; } -export interface CalendarCoreOptions { +export interface CalendarCoreOptions { weekStartsOn?: number; events?: TEvent[]; viewMode: CalendarState['viewMode']; diff --git a/packages/time/src/core/date-picker.ts b/packages/time/src/core/date-picker.ts new file mode 100644 index 0000000..30d96b7 --- /dev/null +++ b/packages/time/src/core/date-picker.ts @@ -0,0 +1,64 @@ +import { Store } from '@tanstack/store'; +import { Temporal } from '@js-temporal/polyfill'; +import { CalendarCore } from './calendar'; +import type { CalendarCoreOptions, CalendarState, Event } from './calendar'; + +export interface DatePickerOptions extends CalendarCoreOptions { + minDate?: Temporal.PlainDate; + maxDate?: Temporal.PlainDate; + onSelectDate?: (date: Temporal.PlainDate) => void; + multiple?: boolean; + range?: boolean; + selectedDates?: Temporal.PlainDate[]; +} + +export interface DatePickerState extends CalendarState { + selectedDates: Map; +} + +export class DatePicker extends CalendarCore { + datePickerStore: Store; + options: DatePickerOptions; + + constructor(options: DatePickerOptions) { + super(options); + this.options = options; + this.datePickerStore = new Store({ + ...this.store.state, + selectedDates: new Map(options.selectedDates?.map(date => [date.toString(), date]) ?? []), + }); + } + + getSelectedDates() { + return Array.from(this.datePickerStore.state.selectedDates.values()); + } + + selectDate(date: Temporal.PlainDate) { + const { multiple, range, minDate, maxDate, onSelectDate } = this.options; + + if (minDate && Temporal.PlainDate.compare(date, minDate) < 0) return; + if (maxDate && Temporal.PlainDate.compare(date, maxDate) > 0) return; + + const selectedDates = new Map(this.datePickerStore.state.selectedDates); + + if (range && selectedDates.size === 1) { + selectedDates.set(date.toString(), date); + } else if (multiple) { + if (selectedDates.has(date.toString())) { + selectedDates.delete(date.toString()); + } else { + selectedDates.set(date.toString(), date); + } + } else { + selectedDates.clear(); + selectedDates.set(date.toString(), date); + } + + this.datePickerStore.setState(prev => ({ + ...prev, + selectedDates, + })); + + onSelectDate?.(date); + } +} From 3f4ef792c3b85122be5248903924d84f081bb295 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 20 Jun 2024 23:27:22 +0200 Subject: [PATCH 074/128] test: imports --- packages/time/src/tests/calendar-core.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 74e6e34..c11605b 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -1,7 +1,7 @@ import { Temporal } from '@js-temporal/polyfill'; import { beforeEach, describe, expect, test, vi } from 'vitest'; -import { CalendarCore } from '../calendar-core'; -import type { CalendarCoreOptions, Event } from '../calendar-core'; +import { CalendarCore } from '../core/calendar'; +import type { CalendarCoreOptions, Event } from '../core/calendar'; describe('CalendarCore', () => { let options: CalendarCoreOptions; From bff5f34133f910b35afb4c8505adfc5fa36ed341 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 20 Jun 2024 23:35:38 +0200 Subject: [PATCH 075/128] docs: typo --- docs/reference/calendar-core.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/calendar-core.md b/docs/reference/calendar-core.md index 8c7b244..22e4b52 100644 --- a/docs/reference/calendar-core.md +++ b/docs/reference/calendar-core.md @@ -55,7 +55,7 @@ The `CalendarCore` class provides a set of functionalities for managing calendar #### Example Usage ```ts -import { CalendarCore, Event } from 'your-calendar-core-package'; +import { CalendarCore, Event } from '@tanstack/time'; import { Temporal } from '@js-temporal/polyfill'; interface MyEvent extends Event { From 57c547efad812ba8384dbc2c4387d9baf008af97 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Fri, 21 Jun 2024 12:42:18 +0200 Subject: [PATCH 076/128] refactor: DatePickerCore --- packages/time/src/core/date-picker.ts | 64 --------------------------- 1 file changed, 64 deletions(-) delete mode 100644 packages/time/src/core/date-picker.ts diff --git a/packages/time/src/core/date-picker.ts b/packages/time/src/core/date-picker.ts deleted file mode 100644 index 30d96b7..0000000 --- a/packages/time/src/core/date-picker.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Store } from '@tanstack/store'; -import { Temporal } from '@js-temporal/polyfill'; -import { CalendarCore } from './calendar'; -import type { CalendarCoreOptions, CalendarState, Event } from './calendar'; - -export interface DatePickerOptions extends CalendarCoreOptions { - minDate?: Temporal.PlainDate; - maxDate?: Temporal.PlainDate; - onSelectDate?: (date: Temporal.PlainDate) => void; - multiple?: boolean; - range?: boolean; - selectedDates?: Temporal.PlainDate[]; -} - -export interface DatePickerState extends CalendarState { - selectedDates: Map; -} - -export class DatePicker extends CalendarCore { - datePickerStore: Store; - options: DatePickerOptions; - - constructor(options: DatePickerOptions) { - super(options); - this.options = options; - this.datePickerStore = new Store({ - ...this.store.state, - selectedDates: new Map(options.selectedDates?.map(date => [date.toString(), date]) ?? []), - }); - } - - getSelectedDates() { - return Array.from(this.datePickerStore.state.selectedDates.values()); - } - - selectDate(date: Temporal.PlainDate) { - const { multiple, range, minDate, maxDate, onSelectDate } = this.options; - - if (minDate && Temporal.PlainDate.compare(date, minDate) < 0) return; - if (maxDate && Temporal.PlainDate.compare(date, maxDate) > 0) return; - - const selectedDates = new Map(this.datePickerStore.state.selectedDates); - - if (range && selectedDates.size === 1) { - selectedDates.set(date.toString(), date); - } else if (multiple) { - if (selectedDates.has(date.toString())) { - selectedDates.delete(date.toString()); - } else { - selectedDates.set(date.toString(), date); - } - } else { - selectedDates.clear(); - selectedDates.set(date.toString(), date); - } - - this.datePickerStore.setState(prev => ({ - ...prev, - selectedDates, - })); - - onSelectDate?.(date); - } -} From 3dc223d4f564efe2053b59c6f69d09d16766eb7b Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 23 Jun 2024 23:26:06 +0200 Subject: [PATCH 077/128] refactor: weekInfoPolyfill --- packages/time/src/core/calendar.ts | 2 + packages/time/src/core/weekInfoPolyfill.ts | 3213 ++++++++++++++++++++ 2 files changed, 3215 insertions(+) create mode 100644 packages/time/src/core/weekInfoPolyfill.ts diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 4412972..daa2aed 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -9,6 +9,8 @@ import type { Properties as CSSProperties } from 'csstype'; import type { GroupDaysByProps } from '../calendar/groupDaysBy'; import type { CalendarState, Day, Event } from '../calendar/types'; +import './weekInfoPolyfill' + export type { CalendarState, Event, Day } from '../calendar/types'; export interface ViewMode { diff --git a/packages/time/src/core/weekInfoPolyfill.ts b/packages/time/src/core/weekInfoPolyfill.ts new file mode 100644 index 0000000..e3e73f3 --- /dev/null +++ b/packages/time/src/core/weekInfoPolyfill.ts @@ -0,0 +1,3213 @@ +interface WeekInfo { + firstDay: number + weekend: number[] + minimalDays: number +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Intl { + interface Locale { + getWeekInfo: () => WeekInfo + } +} + +;(function () { + if (typeof (Intl as any).Locale.prototype.getWeekInfo !== 'function') { + ;(Intl as any).Locale.prototype.getWeekInfo = function () { + const locale = this.toString().toLowerCase() + const weekInfo: Record = { + af_NA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + af_ZA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + af: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ak_GH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ak: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sq_AL: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sq: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + am_ET: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + am: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_DZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_BH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_EG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_IQ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_JO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_KW: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_LB: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_LY: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_MA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_OM: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_QA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_SA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_SD: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_SY: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_TN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_AE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar_YE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hy_AM: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hy: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + as_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + as: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + asa_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + asa: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + az_Cyrl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + az_Latn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + az: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bm_ML: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bm: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + eu_ES: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + eu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + be_BY: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + be: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bem_ZM: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bem: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bez_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bez: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bn_BD: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bn_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bs_BA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bs: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bg_BG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + my_MM: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + my: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ca_ES: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ca: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + tzm_Latn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + tzm: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + chr_US: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + chr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + cgg_UG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + cgg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + zh_Hans: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + zh_Hant: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + zh: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kw_GB: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hr_HR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + cs_CZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + cs: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + da_DK: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + da: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nl_BE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nl_NL: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ebu_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ebu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_AS: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_AU: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_BE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_BZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_BW: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_CA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_GU: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_HK: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_IE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_IL: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_JM: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_MT: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_MH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_MU: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_NA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_NZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_MP: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_PK: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_PH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_SG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_ZA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_TT: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_UM: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_VI: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_GB: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_US: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en_ZW: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + eo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + et_EE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + et: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ee_GH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ee_TG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ee: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fo_FO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fil_PH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fil: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fi_FI: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fi: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_BE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_BJ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_BF: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_BI: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_CM: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_CA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_CF: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_TD: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_KM: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_CG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_CD: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_CI: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_DJ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_GQ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_FR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_GA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_GP: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_GN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_LU: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_MG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_ML: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_MQ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_MC: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_NE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_RW: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_RE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_BL: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_MF: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_SN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_CH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr_TG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ff_SN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ff: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gl_ES: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lg_UG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ka_GE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ka: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + de_AT: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + de_BE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + de_DE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + de_LI: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + de_LU: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + de_CH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + de: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + el_CY: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + el_GR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + el: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gu_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + guz_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + guz: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ha_Latn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ha: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + haw_US: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + haw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + he_IL: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + he: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hi_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hi: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hu_HU: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + is_IS: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + is: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ig_NG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ig: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + id_ID: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + id: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ga_IE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ga: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + it_IT: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + it_CH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + it: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ja_JP: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ja: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kea_CV: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kea: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kab_DZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kab: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kl_GL: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kln_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kln: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kam_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kam: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kn_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kk_Cyrl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + km_KH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + km: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ki_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ki: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rw_RW: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kok_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kok: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ko_KR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ko: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + khq_ML: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + khq: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ses_ML: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ses: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lag_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lag: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lv_LV: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lv: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lt_LT: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lt: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + luo_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + luo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + luy_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + luy: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mk_MK: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + jmc_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + jmc: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kde_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kde: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mg_MG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ms_BN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ms_MY: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ms: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ml_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ml: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mt_MT: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mt: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gv_GB: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gv: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mr_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mas_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mas_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mas: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mer_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mer: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mfe_MU: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mfe: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + naq_NA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + naq: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ne_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ne_NP: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ne: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nd_ZW: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nd: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nb_NO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nb: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nn_NO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nyn_UG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nyn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + or_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + or: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + om_ET: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + om_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + om: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ps_AF: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ps: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fa_AF: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fa_IR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fa: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pl_PL: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pt_BR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pt_GW: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pt_MZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pt_PT: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pt: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pa_Arab: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pa_Guru: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pa: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ro_MD: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ro_RO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ro: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rm_CH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rm: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rof_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rof: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ru_MD: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ru_RU: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ru_UA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ru: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rwk_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rwk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + saq_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + saq: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sg_CF: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + seh_MZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + seh: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sr_Cyrl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sr_Latn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sn_ZW: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ii_CN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ii: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + si_LK: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + si: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sk_SK: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sl_SI: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + xog_UG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + xog: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + so_DJ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + so_ET: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + so_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + so_SO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + so: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_AR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_BO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_CL: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_CO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_CR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_DO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_EC: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_SV: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_GQ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_GT: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_HN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_419: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_MX: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_NI: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_PA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_PY: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_PE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_PR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_ES: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_US: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_UY: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es_VE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sw_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sw_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sv_FI: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sv_SE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sv: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gsw_CH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gsw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + shi_Latn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + shi_Tfng: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + shi: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + dav_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + dav: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ta_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ta_LK: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ta: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + te_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + te: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + teo_KE: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + teo_UG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + teo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + th_TH: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + th: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bo_CN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bo_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ti_ER: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ti_ET: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ti: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + to_TO: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + to: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + tr_TR: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + tr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + uk_UA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + uk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ur_IN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ur_PK: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ur: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + uz_Arab: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + uz_Cyrl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + uz_Latn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + uz: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + vi_VN: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + vi: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + vun_TZ: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + vun: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + cy_GB: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + cy: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + yo_NG: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + yo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + zu_ZA: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + zu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'af-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'am-ET': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-AE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-BH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-DZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-EG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-IQ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-JO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-KW': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-LB': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-LY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-MA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'arn-CL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-OM': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-QA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-SA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-SD': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-SY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-TN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-YE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'as-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'az-az': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'az-Cyrl-AZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'az-Latn-AZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ba-RU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'be-BY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bg-BG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bn-BD': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bn-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bo-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'br-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bs-Cyrl-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bs-Latn-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ca-ES': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'co-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'cs-CZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'cy-GB': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'da-DK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-AT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-CH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-DE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-LI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-LU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'dsb-DE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'dv-MV': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'el-CY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'el-GR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-029': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-AU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-BZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-cb': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-GB': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-IE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-JM': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-MT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-MY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-NZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-PH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-SG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-TT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-US': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-ZW': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-AR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-BO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-CL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-CO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-CR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-DO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-EC': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-ES': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-GT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-HN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-MX': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-NI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-PA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-PE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-PR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-PY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-SV': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-US': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-UY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-VE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'et-EE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'eu-ES': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fa-IR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fi-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fil-PH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fo-FO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-BE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-CH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-LU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-MC': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fy-NL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ga-IE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gd-GB': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gd-ie': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gl-ES': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gsw-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gu-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ha-Latn-NG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'he-IL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hi-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hr-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hr-HR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hsb-DE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hu-HU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hy-AM': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'id-ID': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ig-NG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ii-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'in-ID': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'is-IS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'it-CH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'it-IT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'iu-Cans-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'iu-Latn-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'iw-IL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ja-JP': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ka-GE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'kk-KZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'kl-GL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'km-KH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'kn-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'kok-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ko-KR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ky-KG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'lb-LU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'lo-LA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'lt-LT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'lv-LV': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mi-NZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mk-MK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ml-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mn-MN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mn-Mong-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'moh-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mr-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ms-BN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ms-MY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mt-MT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nb-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ne-NP': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nl-BE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nl-NL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nn-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'no-no': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nso-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'oc-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'or-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'pa-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'pl-PL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'prs-AF': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ps-AF': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'pt-BR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'pt-PT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'qut-GT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'quz-BO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'quz-EC': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'quz-PE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'rm-CH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ro-mo': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ro-RO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ru-mo': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ru-RU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'rw-RW': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sah-RU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sa-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'se-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'se-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'se-SE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'si-LK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sk-SK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sl-SI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sma-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sma-SE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'smj-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'smj-SE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'smn-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sms-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sq-AL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-CS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Cyrl-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Cyrl-CS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Cyrl-ME': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Cyrl-RS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Latn-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Latn-CS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Latn-ME': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Latn-RS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-ME': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-RS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-sp': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sv-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sv-SE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sw-KE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'syr-SY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ta-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'te-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tg-Cyrl-TJ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'th-TH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tk-TM': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tlh-QS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tn-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tr-TR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tt-RU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tzm-Latn-DZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ug-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'uk-UA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ur-PK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'uz-Cyrl-UZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'uz-Latn-UZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'uz-uz': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'vi-VN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'wo-SN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'xh-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'yo-NG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-HK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-MO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-SG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-TW': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zu-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + } + + const match = + weekInfo[locale] || + weekInfo[locale.split('-')[0]] || + weekInfo['default'] + + return { + firstDay: match?.firstDay, + weekend: match?.weekend, + minimalDays: match?.minimalDays, + } + } + } +})() From 2eabee40d5f93441a9a866befa7a1e677bd5a278 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 23 Jun 2024 23:43:29 +0200 Subject: [PATCH 078/128] refactor: weekInfoPolyfill --- packages/time/src/core/calendar.ts | 8 ++++---- packages/time/src/utils/getFirstDayOfWeek.ts | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index daa2aed..834a2e8 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -9,7 +9,7 @@ import type { Properties as CSSProperties } from 'csstype'; import type { GroupDaysByProps } from '../calendar/groupDaysBy'; import type { CalendarState, Day, Event } from '../calendar/types'; -import './weekInfoPolyfill' +import './weekInfoPolyfill'; export type { CalendarState, Event, Day } from '../calendar/types'; @@ -22,7 +22,7 @@ export interface CalendarCoreOptions { weekStartsOn?: number; events?: TEvent[]; viewMode: CalendarState['viewMode']; - locale?: Parameters['0']; + locale?: string; } export interface CalendarApi { @@ -49,7 +49,7 @@ export class CalendarCore { options: CalendarCoreOptions; constructor(options: CalendarCoreOptions) { - this.options = options; + this.options = options; this.store = new Store({ currentPeriod: Temporal.Now.plainDateISO(), viewMode: options.viewMode, @@ -64,7 +64,7 @@ export class CalendarCore { } private getFirstDayOfWeek() { - return getFirstDayOfWeek(this.store.state.currentPeriod.toString(), this.options.weekStartsOn ?? 1); + return getFirstDayOfWeek(this.store.state.currentPeriod.toString(), this.options.locale); } private getCalendarDays() { diff --git a/packages/time/src/utils/getFirstDayOfWeek.ts b/packages/time/src/utils/getFirstDayOfWeek.ts index bb02180..701b1ed 100644 --- a/packages/time/src/utils/getFirstDayOfWeek.ts +++ b/packages/time/src/utils/getFirstDayOfWeek.ts @@ -1,6 +1,8 @@ import { Temporal } from '@js-temporal/polyfill' -export const getFirstDayOfWeek = (currWeek: string, weekStartsOn: number) => { - const date = Temporal.PlainDate.from(currWeek) - return date.subtract({ days: (date.dayOfWeek - weekStartsOn + 7) % 7 }) +export const getFirstDayOfWeek = (currWeek: string, locale: string = 'en-US') => { + const date = Temporal.PlainDate.from(currWeek); + const loc = new Intl.Locale(locale); + const { firstDay } = loc.getWeekInfo(); + return date.subtract({ days: (date.dayOfWeek - firstDay + 7) % 7 }); } From 564c4e7d58be50b726dce17483e9364c5fcd1b04 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 24 Jun 2024 00:31:41 +0200 Subject: [PATCH 079/128] refactor: weekInfoPolyfill --- packages/time/src/core/weekInfoPolyfill.ts | 1590 ++------------------ 1 file changed, 120 insertions(+), 1470 deletions(-) diff --git a/packages/time/src/core/weekInfoPolyfill.ts b/packages/time/src/core/weekInfoPolyfill.ts index e3e73f3..70f0558 100644 --- a/packages/time/src/core/weekInfoPolyfill.ts +++ b/packages/time/src/core/weekInfoPolyfill.ts @@ -16,1832 +16,602 @@ declare namespace Intl { ;(Intl as any).Locale.prototype.getWeekInfo = function () { const locale = this.toString().toLowerCase() const weekInfo: Record = { - af_NA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - af_ZA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - af: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ak_GH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ak: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sq_AL: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - am_ET: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - am: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_DZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_BH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_EG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_IQ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_JO: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_KW: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_LB: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_LY: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_MA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_OM: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_QA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_SA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_SD: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_SY: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_TN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_AE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar_YE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hy_AM: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hy: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - as_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - as: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - asa_TZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - asa: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - az_Cyrl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - az_Latn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - az: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bm_ML: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bm: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - eu_ES: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - eu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - be_BY: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - be: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bem_ZM: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bem: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bez_TZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bez: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bn_BD: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bn_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bs_BA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bs: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bg_BG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - my_MM: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - my: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ca_ES: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ca: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - tzm_Latn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - tzm: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - chr_US: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - chr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cgg_UG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cgg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - zh_Hans: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - zh_Hant: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - zh: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kw_GB: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hr_HR: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cs_CZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cs: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - da_DK: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - da: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nl_BE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nl_NL: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ebu_KE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ebu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_AS: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_AU: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_BE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_BZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_BW: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_CA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_GU: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_HK: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_IE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_IL: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_JM: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_MT: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_MH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_MU: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_NA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_NZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_MP: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_PK: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_PH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_SG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_ZA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_TT: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_UM: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_VI: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_GB: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_US: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en_ZW: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - eo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - et_EE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - et: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ee_GH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ee_TG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ee: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fo_FO: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fil_PH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fil: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fi_FI: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_BE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_BJ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_BF: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_BI: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_CM: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_CA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_CF: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_TD: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_KM: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_CG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_CD: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_CI: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_DJ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_GQ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_FR: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_GA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_GP: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_GN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_LU: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_MG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_ML: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_MQ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_MC: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_NE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_RW: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_RE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_BL: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_MF: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_SN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_CH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr_TG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ff_SN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ff: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gl_ES: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lg_UG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ka_GE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ka: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - de_AT: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - de_BE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - de_DE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - de_LI: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - de_LU: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - de_CH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - de: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - el_CY: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - el_GR: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - el: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gu_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - guz_KE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - guz: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ha_Latn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ha: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - haw_US: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - haw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - he_IL: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - he: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hi_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hu_HU: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - is_IS: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - is: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ig_NG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ig: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - id_ID: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - id: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ga_IE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ga: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - it_IT: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - it_CH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - it: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ja_JP: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ja: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kea_CV: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kea: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kab_DZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kab: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kl_GL: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kln_KE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kln: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kam_KE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kam: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kn_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kk_Cyrl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - km_KH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - km: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ki_KE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ki: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rw_RW: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kok_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kok: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ko_KR: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ko: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - khq_ML: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - khq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ses_ML: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ses: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lag_TZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lag: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lv_LV: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lv: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lt_LT: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lt: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - luo_KE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - luo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - luy_KE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - luy: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mk_MK: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - jmc_TZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - jmc: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kde_TZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kde: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mg_MG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ms_BN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ms_MY: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ms: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ml_IN: { + af: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ml: { + ak: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mt_MT: { + sq: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mt: { + am: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - gv_GB: { + ar: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - gv: { + hy: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mr_IN: { + as: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mr: { + asa: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mas_KE: { + az: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mas_TZ: { + bm: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mas: { + eu: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mer_KE: { + be: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mer: { + bem: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mfe_MU: { + bez: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - mfe: { + bn: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - naq_NA: { + bs: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - naq: { + bg: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ne_IN: { + my: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ne_NP: { + ca: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ne: { + tzm: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - nd_ZW: { + chr: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - nd: { + cgg: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - nb_NO: { + zh: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - nb: { + kw: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - nn_NO: { + hr: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - nn: { + cs: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - nyn_UG: { + da: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - nyn: { + nl: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - or_IN: { + ebu: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - or: { + en: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - om_ET: { + eo: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - om_KE: { + et: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - om: { + ee: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ps_AF: { + fo: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ps: { + fil: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - fa_AF: { + fi: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - fa_IR: { + fr: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - fa: { + ff: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pl_PL: { + gl: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pl: { + lg: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pt_BR: { + ka: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pt_GW: { + de: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pt_MZ: { + el: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pt_PT: { + gu: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pt: { + guz: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pa_Arab: { + ha: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pa_Guru: { + haw: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - pa: { + he: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ro_MD: { + hi: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ro_RO: { + hu: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ro: { + is: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - rm_CH: { + ig: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - rm: { + id: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - rof_TZ: { + ga: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - rof: { + it: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ru_MD: { + ja: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ru_RU: { + kea: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ru_UA: { + kab: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ru: { + kl: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - rwk_TZ: { + kln: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - rwk: { + kam: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - saq_KE: { + kn: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - saq: { + kk: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sg_CF: { + km: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sg: { + ki: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - seh_MZ: { + rw: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - seh: { + kok: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sr_Cyrl: { + ko: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sr_Latn: { + khq: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sr: { + ses: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sn_ZW: { + lag: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sn: { + lv: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ii_CN: { + lt: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ii: { + luo: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - si_LK: { + luy: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - si: { + mk: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sk_SK: { + jmc: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sk: { + kde: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sl_SI: { + mg: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sl: { + ms: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - xog_UG: { + ml: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - xog: { + mt: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - so_DJ: { + gv: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - so_ET: { + mr: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - so_KE: { + mas: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - so_SO: { + mer: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - so: { + mfe: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_AR: { + naq: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_BO: { + ne: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_CL: { + nd: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_CO: { + nb: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_CR: { + nn: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_DO: { + nyn: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_EC: { + or: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_SV: { + om: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_GQ: { + ps: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_GT: { + fa: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_HN: { + pl: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_419: { + pt: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_MX: { + pa: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_NI: { + ro: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_PA: { + rm: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_PY: { + rof: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_PE: { + ru: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_PR: { + rwk: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_ES: { + saq: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_US: { + sg: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_UY: { + seh: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es_VE: { + sr: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - es: { + sn: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sw_KE: { + ii: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sw_TZ: { + si: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sw: { + sk: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sv_FI: { + sl: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sv_SE: { + xog: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - sv: { + so: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - gsw_CH: { + es: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - gsw: { + sw: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - shi_Latn: { + sv: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - shi_Tfng: { + gsw: { firstDay: 1, weekend: [6, 7], minimalDays: 4, @@ -1851,206 +621,86 @@ declare namespace Intl { weekend: [6, 7], minimalDays: 4, }, - dav_KE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, dav: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ta_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ta_LK: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, ta: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - te_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, te: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - teo_KE: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - teo_UG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, teo: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - th_TH: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, th: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - bo_CN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bo_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, bo: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ti_ER: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ti_ET: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, ti: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - to_TO: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, to: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - tr_TR: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, tr: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - uk_UA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, uk: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - ur_IN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ur_PK: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, ur: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - uz_Arab: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - uz_Cyrl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - uz_Latn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, uz: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - vi_VN: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, vi: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - vun_TZ: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, vun: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - cy_GB: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, cy: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - yo_NG: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, yo: { firstDay: 1, weekend: [6, 7], minimalDays: 4, }, - zu_ZA: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, zu: { firstDay: 1, weekend: [6, 7], From 1a5f1937ebd80184764ab1332c7b1eb46d46d4d6 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 24 Jun 2024 10:13:56 +0200 Subject: [PATCH 080/128] refactor: weekInfoPolyfill --- packages/time/src/core/weekInfoPolyfill.ts | 1 + packages/time/src/utils/getFirstDayOfWeek.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/time/src/core/weekInfoPolyfill.ts b/packages/time/src/core/weekInfoPolyfill.ts index 70f0558..28c7351 100644 --- a/packages/time/src/core/weekInfoPolyfill.ts +++ b/packages/time/src/core/weekInfoPolyfill.ts @@ -8,6 +8,7 @@ interface WeekInfo { declare namespace Intl { interface Locale { getWeekInfo: () => WeekInfo + weekInfo?: WeekInfo } } diff --git a/packages/time/src/utils/getFirstDayOfWeek.ts b/packages/time/src/utils/getFirstDayOfWeek.ts index 701b1ed..1b33e98 100644 --- a/packages/time/src/utils/getFirstDayOfWeek.ts +++ b/packages/time/src/utils/getFirstDayOfWeek.ts @@ -3,6 +3,6 @@ import { Temporal } from '@js-temporal/polyfill' export const getFirstDayOfWeek = (currWeek: string, locale: string = 'en-US') => { const date = Temporal.PlainDate.from(currWeek); const loc = new Intl.Locale(locale); - const { firstDay } = loc.getWeekInfo(); + const { firstDay } = loc.weekInfo || loc.getWeekInfo(); return date.subtract({ days: (date.dayOfWeek - firstDay + 7) % 7 }); } From 3331c174e061d9b45fb193fb46b3a11e5951a0bf Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 24 Jun 2024 21:46:29 +0200 Subject: [PATCH 081/128] refactor: date defaults --- packages/time/src/core/calendar.ts | 11 ++++++----- packages/time/src/tests/calendar-core.test.ts | 2 -- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 834a2e8..5e5fa9e 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -19,10 +19,11 @@ export interface ViewMode { } export interface CalendarCoreOptions { - weekStartsOn?: number; events?: TEvent[]; viewMode: CalendarState['viewMode']; locale?: string; + timeZone?: string; + calendar?: string; } export interface CalendarApi { @@ -71,7 +72,7 @@ export class CalendarCore { const start = this.store.state.viewMode.unit === 'month' ? this.getFirstDayOfMonth().subtract({ - days: (this.getFirstDayOfMonth().dayOfWeek - (this.options.weekStartsOn ?? 1) + 7) % 7, + days: (this.getFirstDayOfMonth().dayOfWeek - (this.getFirstDayOfWeek().dayOfWeek + 1) + 7) % 7, }) : this.store.state.currentPeriod; @@ -82,7 +83,7 @@ export class CalendarCore { .add({ months: this.store.state.viewMode.value }) .subtract({ days: 1 }); const lastDayOfMonthWeekDay = - (lastDayOfMonth.dayOfWeek - (this.options.weekStartsOn ?? 1) + 7) % 7; + (lastDayOfMonth.dayOfWeek - (this.getFirstDayOfWeek().dayOfWeek + 1) + 7) % 7; end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }); break; } @@ -162,7 +163,7 @@ export class CalendarCore { const baseDate = Temporal.PlainDate.from('2024-01-01'); return Array.from({ length: 7 }).map((_, i) => baseDate - .add({ days: (i + (this.options.weekStartsOn ?? 1) - 1) % 7 }) + .add({ days: (i + (this.getFirstDayOfWeek().dayOfWeek + 1)) % 7 }) .toLocaleString(this.options.locale, { weekday: 'short' }), ); } @@ -283,6 +284,6 @@ export class CalendarCore { } groupDaysBy({ days, unit, fillMissingDays = true }: Omit, 'weekStartsOn'>) { - return groupDaysBy({ days, unit, fillMissingDays, weekStartsOn: this.options.weekStartsOn ?? 1 } as GroupDaysByProps); + return groupDaysBy({ days, unit, fillMissingDays, weekStartsOn: this.getFirstDayOfWeek().dayOfWeek } as GroupDaysByProps); } } diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index c11605b..29f4234 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -11,7 +11,6 @@ describe('CalendarCore', () => { beforeEach(() => { options = { - weekStartsOn: 1, viewMode: { value: 1, unit: 'month' }, events: [ { @@ -100,7 +99,6 @@ describe('CalendarCore', () => { vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:00:00')); const coreOptions: CalendarCoreOptions = { - weekStartsOn: 1, viewMode: { value: 1, unit: 'week' }, events: [], }; From e1f9c12aea6fb5225c8ad1e63ec2c929fb86fd50 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 24 Jun 2024 22:53:48 +0200 Subject: [PATCH 082/128] refactor: date defaults --- .../time/src/calendar/splitMultiDayEvents.ts | 8 ++--- packages/time/src/core/calendar.ts | 31 ++++++++++++------- packages/time/src/tests/calendar-core.test.ts | 29 +++++++++++++---- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts index ebb0c2a..e5bdddd 100644 --- a/packages/time/src/calendar/splitMultiDayEvents.ts +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -1,13 +1,13 @@ import { Temporal } from '@js-temporal/polyfill'; import type { Event } from './types'; -export const splitMultiDayEvents = (event: TEvent): TEvent[] => { - const startDate = Temporal.PlainDateTime.from(event.startDate); - const endDate = Temporal.PlainDateTime.from(event.endDate); +export const splitMultiDayEvents = (event: TEvent, timeZone: Temporal.TimeZoneLike): TEvent[] => { + const startDate = event.startDate.toZonedDateTime(timeZone); + const endDate = event.endDate.toZonedDateTime(timeZone); const events: TEvent[] = []; let currentDay = startDate; - while (Temporal.PlainDate.compare(currentDay.toPlainDate(), endDate.toPlainDate()) < 0) { + while (Temporal.ZonedDateTime.compare(currentDay, endDate) < 0) { const startOfCurrentDay = currentDay.with({ hour: 0, minute: 0, second: 0, millisecond: 0 }); const endOfCurrentDay = currentDay.with({ hour: 23, minute: 59, second: 59, millisecond: 999 }); diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 5e5fa9e..f70a736 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -5,6 +5,7 @@ import { generateDateRange } from '../calendar/generateDateRange'; import { splitMultiDayEvents } from '../calendar/splitMultiDayEvents'; import { getEventProps } from '../calendar/getEventProps'; import { groupDaysBy } from '../calendar/groupDaysBy'; +import { getDateDefaults } from '../utils/dateDefaults'; import type { Properties as CSSProperties } from 'csstype'; import type { GroupDaysByProps } from '../calendar/groupDaysBy'; import type { CalendarState, Day, Event } from '../calendar/types'; @@ -19,7 +20,7 @@ export interface ViewMode { } export interface CalendarCoreOptions { - events?: TEvent[]; + events?: TEvent[] | null; viewMode: CalendarState['viewMode']; locale?: string; timeZone?: string; @@ -47,14 +48,22 @@ export interface CalendarApi { export class CalendarCore { store: Store; - options: CalendarCoreOptions; + options: Required>; constructor(options: CalendarCoreOptions) { - this.options = options; + const defaults = getDateDefaults(); + this.options = { + ...options, + locale: options.locale || defaults.locale, + timeZone: options.timeZone || defaults.timeZone, + calendar: options.calendar || defaults.calendar, + events: options.events || null, + }; + this.store = new Store({ - currentPeriod: Temporal.Now.plainDateISO(), + currentPeriod: Temporal.Now.plainDateISO().withCalendar(this.options.calendar), viewMode: options.viewMode, - currentTime: Temporal.Now.plainDateTimeISO(), + currentTime: Temporal.Now.plainDateTimeISO(this.options.timeZone), }); } @@ -111,15 +120,15 @@ export class CalendarCore { private getEventMap() { const map = new Map(); this.options.events?.forEach((event) => { - const eventStartDate = Temporal.PlainDateTime.from(event.startDate); - const eventEndDate = Temporal.PlainDateTime.from(event.endDate); + const eventStartDate = event.startDate.toZonedDateTime(this.options.timeZone); + const eventEndDate = event.endDate.toZonedDateTime(this.options.timeZone); if ( - Temporal.PlainDate.compare( - eventStartDate.toPlainDate(), - eventEndDate.toPlainDate(), + Temporal.ZonedDateTime.compare( + eventStartDate, + eventEndDate, ) !== 0 ) { - const splitEvents = splitMultiDayEvents(event); + const splitEvents = splitMultiDayEvents(event, this.options.timeZone); splitEvents.forEach((splitEvent) => { const splitKey = splitEvent.startDate.toString().split('T')[0]; if (splitKey) { diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 29f4234..22f6697 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -8,6 +8,7 @@ describe('CalendarCore', () => { let calendarCore: CalendarCore; const mockDate = Temporal.PlainDate.from('2023-06-15'); const mockDateTime = Temporal.PlainDateTime.from('2023-06-15T10:00'); + const mockTimeZone = 'America/New_York'; beforeEach(() => { options = { @@ -26,6 +27,7 @@ describe('CalendarCore', () => { title: 'Event 2', }, ], + timeZone: mockTimeZone, }; calendarCore = new CalendarCore(options); vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate); @@ -89,7 +91,7 @@ describe('CalendarCore', () => { const initialTime = calendarCore.store.state.currentTime; const newMockDateTime = initialTime.add({ minutes: 1 }); vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(newMockDateTime); - + calendarCore.updateCurrentTime(); const updatedTime = calendarCore.store.state.currentTime; expect(updatedTime).toEqual(newMockDateTime); @@ -101,6 +103,7 @@ describe('CalendarCore', () => { const coreOptions: CalendarCoreOptions = { viewMode: { value: 1, unit: 'week' }, events: [], + timeZone: mockTimeZone, }; calendarCore = new CalendarCore(coreOptions); @@ -116,18 +119,32 @@ describe('CalendarCore', () => { }); }); - test('should update the current time on', () => { + test('should update the current time', () => { const initialTime = calendarCore.store.state.currentTime; const newMockDateTime = initialTime.add({ minutes: 1 }); vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(newMockDateTime); - + calendarCore.updateCurrentTime(); - expect(initialTime).toEqual(newMockDateTime); - }) + expect(calendarCore.store.state.currentTime).toEqual(newMockDateTime); + }); test('should group days correctly', () => { const daysWithEvents = calendarCore.getDaysWithEvents(); - const groupedDays = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'month'}); + const groupedDays = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'month' }); expect(groupedDays.length).toBeGreaterThan(0); }); + + test('should initialize with the correct time zone', () => { + expect(calendarCore.options.timeZone).toBe(mockTimeZone); + }); + + test('should respect custom calendar', () => { + const customCalendar = 'islamic-civil'; + options.calendar = customCalendar; + calendarCore = new CalendarCore(options); + + const today = Temporal.Now.plainDateISO(customCalendar); + expect(calendarCore.store.state.currentPeriod.calendarId).toBe(customCalendar); + expect(calendarCore.store.state.currentPeriod).toEqual(today); + }); }); From 1dfecfbdba39238040c85fed2b3d925f15640253 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 24 Jun 2024 23:02:26 +0200 Subject: [PATCH 083/128] refactor: date defaults --- packages/time/src/calendar/splitMultiDayEvents.ts | 4 ++-- packages/time/src/calendar/types.ts | 4 ++-- packages/time/src/core/calendar.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts index e5bdddd..0d2088d 100644 --- a/packages/time/src/calendar/splitMultiDayEvents.ts +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -2,8 +2,8 @@ import { Temporal } from '@js-temporal/polyfill'; import type { Event } from './types'; export const splitMultiDayEvents = (event: TEvent, timeZone: Temporal.TimeZoneLike): TEvent[] => { - const startDate = event.startDate.toZonedDateTime(timeZone); - const endDate = event.endDate.toZonedDateTime(timeZone); + const startDate = event.startDate instanceof Temporal.PlainDateTime ? event.startDate.toZonedDateTime(timeZone) : event.startDate; + const endDate = event.endDate instanceof Temporal.PlainDateTime ? event.endDate.toZonedDateTime(timeZone) : event.endDate; const events: TEvent[] = []; let currentDay = startDate; diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index 8da9ad3..c3376b3 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -2,8 +2,8 @@ import type { Temporal } from "@js-temporal/polyfill" export interface Event { id: string; - startDate: Temporal.PlainDateTime; - endDate: Temporal.PlainDateTime; + startDate: Temporal.PlainDateTime | Temporal.ZonedDateTime; + endDate: Temporal.PlainDateTime | Temporal.ZonedDateTime; title: string; } diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index f70a736..c220b44 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -120,8 +120,8 @@ export class CalendarCore { private getEventMap() { const map = new Map(); this.options.events?.forEach((event) => { - const eventStartDate = event.startDate.toZonedDateTime(this.options.timeZone); - const eventEndDate = event.endDate.toZonedDateTime(this.options.timeZone); + const eventStartDate = event.startDate instanceof Temporal.PlainDateTime ? event.startDate.toZonedDateTime(this.options.timeZone) : event.startDate; + const eventEndDate = event.endDate instanceof Temporal.PlainDateTime ? event.endDate.toZonedDateTime(this.options.timeZone) : event.endDate; if ( Temporal.ZonedDateTime.compare( eventStartDate, From 277f416cc4e363e0170ead2f8bee347395e3718b Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 25 Jun 2024 11:25:45 +0200 Subject: [PATCH 084/128] refactor: types --- packages/time/src/core/calendar.ts | 6 +++--- packages/time/src/utils/dateDefaults.ts | 8 +++++--- packages/time/src/utils/getFirstDayOfWeek.ts | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index c220b44..2a697b5 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -22,9 +22,9 @@ export interface ViewMode { export interface CalendarCoreOptions { events?: TEvent[] | null; viewMode: CalendarState['viewMode']; - locale?: string; - timeZone?: string; - calendar?: string; + locale?: Intl.UnicodeBCP47LocaleIdentifier; + timeZone?: Temporal.TimeZoneLike; + calendar?: Temporal.CalendarLike; } export interface CalendarApi { diff --git a/packages/time/src/utils/dateDefaults.ts b/packages/time/src/utils/dateDefaults.ts index 0dd3f98..0ac14ae 100644 --- a/packages/time/src/utils/dateDefaults.ts +++ b/packages/time/src/utils/dateDefaults.ts @@ -1,7 +1,9 @@ +import type { Temporal } from "@js-temporal/polyfill"; + export interface IDateDefaults { - calendar: string; - locale: string; - timeZone: string; + calendar: Temporal.CalendarLike; + locale: Intl.UnicodeBCP47LocaleIdentifier; + timeZone: Temporal.TimeZoneLike; } const { diff --git a/packages/time/src/utils/getFirstDayOfWeek.ts b/packages/time/src/utils/getFirstDayOfWeek.ts index 1b33e98..65f71e3 100644 --- a/packages/time/src/utils/getFirstDayOfWeek.ts +++ b/packages/time/src/utils/getFirstDayOfWeek.ts @@ -1,6 +1,6 @@ import { Temporal } from '@js-temporal/polyfill' -export const getFirstDayOfWeek = (currWeek: string, locale: string = 'en-US') => { +export const getFirstDayOfWeek = (currWeek: string, locale: Intl.UnicodeBCP47LocaleIdentifier | Intl.Locale = 'en-US') => { const date = Temporal.PlainDate.from(currWeek); const loc = new Intl.Locale(locale); const { firstDay } = loc.weekInfo || loc.getWeekInfo(); From 4131b8e56424f62f4bc4b983edb1fb515b204025 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 25 Jun 2024 11:43:24 +0200 Subject: [PATCH 085/128] docs: jsdocs --- packages/time/src/core/calendar.ts | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 2a697b5..81b414b 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -14,38 +14,77 @@ import './weekInfoPolyfill'; export type { CalendarState, Event, Day } from '../calendar/types'; +/** + * Represents the configuration for the current viewing mode of a calendar, + * specifying the scale and unit of time. + */ export interface ViewMode { + /** The number of units for the view mode. */ value: number; + /** The unit of time that the calendar view should display (month, week, or day). */ unit: 'month' | 'week' | 'day'; } +/** + * Configuration options for initializing a CalendarCore instance, allowing customization + * of events, locale, time zone, and the calendar system. + * @template TEvent - Specifies the event type, extending a base Event type. + */ export interface CalendarCoreOptions { + /** An optional array of events to be handled by the calendar. */ events?: TEvent[] | null; + /** The initial view mode configuration of the calendar. */ viewMode: CalendarState['viewMode']; + /** Optional locale for date formatting. Uses a BCP 47 language tag. */ locale?: Intl.UnicodeBCP47LocaleIdentifier; + /** Optional time zone specification for the calendar. */ timeZone?: Temporal.TimeZoneLike; + /** Optional calendar system to be used. */ calendar?: Temporal.CalendarLike; } +/** + * The API surface provided by CalendarCore, allowing interaction with the calendar's state + * and manipulation of its settings and data. + * @template TEvent - The type of events handled by the calendar. + */ export interface CalendarApi { + /** The currently focused date period in the calendar. */ currentPeriod: CalendarState['currentPeriod']; + /** The current view mode of the calendar. */ viewMode: CalendarState['viewMode']; + /** The current date and time according to the calendar's time zone. */ currentTime: CalendarState['currentTime']; + /** An array of days, each potentially containing events. */ days: Array>; + /** An array of names for the days of the week, localized to the calendar's locale. */ daysNames: string[]; + /** Navigates to the previous period according to the current view mode. */ goToPreviousPeriod: () => void; + /** Navigates to the next period according to the current view mode. */ goToNextPeriod: () => void; + /** Resets the view to the current period based on today's date. */ goToCurrentPeriod: () => void; + /** Navigates to a specific date. */ goToSpecificPeriod: (date: Temporal.PlainDate) => void; + /** Changes the current view mode of the calendar. */ changeViewMode: (newViewMode: CalendarState['viewMode']) => void; + /** Retrieves styling properties for a specific event, identified by ID. */ getEventProps: (id: Event['id']) => { style: CSSProperties } | null; + /** Provides properties for the marker indicating the current time. */ getCurrentTimeMarkerProps: () => { style: CSSProperties; currentTime: string | undefined; }; + /** Groups days by a specified unit. */ groupDaysBy: (props: Omit, 'weekStartsOn'>) => (Day | null)[][]; } +/** + * Core functionality for a calendar system, managing the state and operations of the calendar, + * such as navigating through time periods, handling events, and adjusting settings. + * @template TEvent - The type of events managed by the calendar. + */ export class CalendarCore { store: Store; options: Required>; From b7f67f09e125371e18e4d8b7f5a4510a0d842ee5 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 25 Jun 2024 11:58:02 +0200 Subject: [PATCH 086/128] refactor: types --- packages/time/src/calendar/types.ts | 2 +- packages/time/src/core/calendar.ts | 330 ++++++++++++++++------------ 2 files changed, 196 insertions(+), 136 deletions(-) diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index c3376b3..08ffe7e 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -7,7 +7,7 @@ export interface Event { title: string; } -export interface CalendarState { +export interface CalendarStore { currentPeriod: Temporal.PlainDate viewMode: { value: number diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 81b414b..50fe6b8 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -1,18 +1,18 @@ -import { Store } from '@tanstack/store'; -import { Temporal } from '@js-temporal/polyfill'; -import { getFirstDayOfMonth, getFirstDayOfWeek } from '../utils'; -import { generateDateRange } from '../calendar/generateDateRange'; -import { splitMultiDayEvents } from '../calendar/splitMultiDayEvents'; -import { getEventProps } from '../calendar/getEventProps'; -import { groupDaysBy } from '../calendar/groupDaysBy'; -import { getDateDefaults } from '../utils/dateDefaults'; -import type { Properties as CSSProperties } from 'csstype'; -import type { GroupDaysByProps } from '../calendar/groupDaysBy'; -import type { CalendarState, Day, Event } from '../calendar/types'; - -import './weekInfoPolyfill'; - -export type { CalendarState, Event, Day } from '../calendar/types'; +import { Store } from '@tanstack/store' +import { Temporal } from '@js-temporal/polyfill' +import { getFirstDayOfMonth, getFirstDayOfWeek } from '../utils' +import { generateDateRange } from '../calendar/generateDateRange' +import { splitMultiDayEvents } from '../calendar/splitMultiDayEvents' +import { getEventProps } from '../calendar/getEventProps' +import { groupDaysBy } from '../calendar/groupDaysBy' +import { getDateDefaults } from '../utils/dateDefaults' +import type { Properties as CSSProperties } from 'csstype' +import type { GroupDaysByProps } from '../calendar/groupDaysBy' +import type { CalendarStore, Day, Event } from '../calendar/types' + +import './weekInfoPolyfill' + +export type { CalendarStore, Event, Day } from '../calendar/types' /** * Represents the configuration for the current viewing mode of a calendar, @@ -20,9 +20,9 @@ export type { CalendarState, Event, Day } from '../calendar/types'; */ export interface ViewMode { /** The number of units for the view mode. */ - value: number; + value: number /** The unit of time that the calendar view should display (month, week, or day). */ - unit: 'month' | 'week' | 'day'; + unit: 'month' | 'week' | 'day' } /** @@ -32,15 +32,15 @@ export interface ViewMode { */ export interface CalendarCoreOptions { /** An optional array of events to be handled by the calendar. */ - events?: TEvent[] | null; + events?: TEvent[] | null /** The initial view mode configuration of the calendar. */ - viewMode: CalendarState['viewMode']; + viewMode: CalendarStore['viewMode'] /** Optional locale for date formatting. Uses a BCP 47 language tag. */ - locale?: Intl.UnicodeBCP47LocaleIdentifier; + locale?: Intl.UnicodeBCP47LocaleIdentifier /** Optional time zone specification for the calendar. */ - timeZone?: Temporal.TimeZoneLike; + timeZone?: Temporal.TimeZoneLike /** Optional calendar system to be used. */ - calendar?: Temporal.CalendarLike; + calendar?: Temporal.CalendarLike } /** @@ -48,245 +48,290 @@ export interface CalendarCoreOptions { * and manipulation of its settings and data. * @template TEvent - The type of events handled by the calendar. */ -export interface CalendarApi { - /** The currently focused date period in the calendar. */ - currentPeriod: CalendarState['currentPeriod']; - /** The current view mode of the calendar. */ - viewMode: CalendarState['viewMode']; - /** The current date and time according to the calendar's time zone. */ - currentTime: CalendarState['currentTime']; - /** An array of days, each potentially containing events. */ - days: Array>; - /** An array of names for the days of the week, localized to the calendar's locale. */ - daysNames: string[]; +interface CalendarActions { /** Navigates to the previous period according to the current view mode. */ - goToPreviousPeriod: () => void; + goToPreviousPeriod: () => void /** Navigates to the next period according to the current view mode. */ - goToNextPeriod: () => void; + goToNextPeriod: () => void /** Resets the view to the current period based on today's date. */ - goToCurrentPeriod: () => void; + goToCurrentPeriod: () => void /** Navigates to a specific date. */ - goToSpecificPeriod: (date: Temporal.PlainDate) => void; + goToSpecificPeriod: (date: Temporal.PlainDate) => void /** Changes the current view mode of the calendar. */ - changeViewMode: (newViewMode: CalendarState['viewMode']) => void; + changeViewMode: (newViewMode: CalendarStore['viewMode']) => void /** Retrieves styling properties for a specific event, identified by ID. */ - getEventProps: (id: Event['id']) => { style: CSSProperties } | null; + getEventProps: (id: Event['id']) => { style: CSSProperties } | null /** Provides properties for the marker indicating the current time. */ getCurrentTimeMarkerProps: () => { - style: CSSProperties; - currentTime: string | undefined; - }; + style: CSSProperties + currentTime: string | undefined + } /** Groups days by a specified unit. */ - groupDaysBy: (props: Omit, 'weekStartsOn'>) => (Day | null)[][]; + groupDaysBy: ( + props: Omit, 'weekStartsOn'>, + ) => (Day | null)[][] +} + +interface CalendarState { + /** The currently focused date period in the calendar. */ + currentPeriod: CalendarStore['currentPeriod'] + /** The current view mode of the calendar. */ + viewMode: CalendarStore['viewMode'] + /** The current date and time according to the calendar's time zone. */ + currentTime: CalendarStore['currentTime'] + /** An array of days, each potentially containing events. */ + days: Array> + /** An array of names for the days of the week, localized to the calendar's locale. */ + daysNames: string[] } +export interface CalendarApi + extends CalendarActions, + CalendarState {} + /** * Core functionality for a calendar system, managing the state and operations of the calendar, * such as navigating through time periods, handling events, and adjusting settings. * @template TEvent - The type of events managed by the calendar. */ -export class CalendarCore { - store: Store; - options: Required>; +export class CalendarCore + implements CalendarActions +{ + store: Store + options: Required> constructor(options: CalendarCoreOptions) { - const defaults = getDateDefaults(); + const defaults = getDateDefaults() this.options = { ...options, locale: options.locale || defaults.locale, timeZone: options.timeZone || defaults.timeZone, calendar: options.calendar || defaults.calendar, events: options.events || null, - }; - - this.store = new Store({ - currentPeriod: Temporal.Now.plainDateISO().withCalendar(this.options.calendar), + } + + this.store = new Store({ + currentPeriod: Temporal.Now.plainDateISO().withCalendar( + this.options.calendar, + ), viewMode: options.viewMode, currentTime: Temporal.Now.plainDateTimeISO(this.options.timeZone), - }); + }) } private getFirstDayOfMonth() { return getFirstDayOfMonth( - this.store.state.currentPeriod.toString({ calendarName: 'auto' }).substring(0, 7), - ); + this.store.state.currentPeriod + .toString({ calendarName: 'auto' }) + .substring(0, 7), + ) } private getFirstDayOfWeek() { - return getFirstDayOfWeek(this.store.state.currentPeriod.toString(), this.options.locale); + return getFirstDayOfWeek( + this.store.state.currentPeriod.toString(), + this.options.locale, + ) } private getCalendarDays() { const start = this.store.state.viewMode.unit === 'month' ? this.getFirstDayOfMonth().subtract({ - days: (this.getFirstDayOfMonth().dayOfWeek - (this.getFirstDayOfWeek().dayOfWeek + 1) + 7) % 7, + days: + (this.getFirstDayOfMonth().dayOfWeek - + (this.getFirstDayOfWeek().dayOfWeek + 1) + + 7) % + 7, }) - : this.store.state.currentPeriod; + : this.store.state.currentPeriod - let end; + let end switch (this.store.state.viewMode.unit) { case 'month': { const lastDayOfMonth = this.getFirstDayOfMonth() .add({ months: this.store.state.viewMode.value }) - .subtract({ days: 1 }); + .subtract({ days: 1 }) const lastDayOfMonthWeekDay = - (lastDayOfMonth.dayOfWeek - (this.getFirstDayOfWeek().dayOfWeek + 1) + 7) % 7; - end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }); - break; + (lastDayOfMonth.dayOfWeek - + (this.getFirstDayOfWeek().dayOfWeek + 1) + + 7) % + 7 + end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay }) + break } case 'week': { - end = this.getFirstDayOfWeek().add({ days: 7 * this.store.state.viewMode.value - 1 }); - break; + end = this.getFirstDayOfWeek().add({ + days: 7 * this.store.state.viewMode.value - 1, + }) + break } case 'day': { - end = this.store.state.currentPeriod.add({ days: this.store.state.viewMode.value - 1 }); - break; + end = this.store.state.currentPeriod.add({ + days: this.store.state.viewMode.value - 1, + }) + break } } - const allDays = generateDateRange(start, end); - const startMonth = this.store.state.currentPeriod.month; + const allDays = generateDateRange(start, end) + const startMonth = this.store.state.currentPeriod.month const endMonth = this.store.state.currentPeriod.add({ months: this.store.state.viewMode.value - 1, - }).month; + }).month return allDays.filter( (day) => day.month >= startMonth && day.month <= endMonth, - ); + ) } private getEventMap() { - const map = new Map(); + const map = new Map() this.options.events?.forEach((event) => { - const eventStartDate = event.startDate instanceof Temporal.PlainDateTime ? event.startDate.toZonedDateTime(this.options.timeZone) : event.startDate; - const eventEndDate = event.endDate instanceof Temporal.PlainDateTime ? event.endDate.toZonedDateTime(this.options.timeZone) : event.endDate; - if ( - Temporal.ZonedDateTime.compare( - eventStartDate, - eventEndDate, - ) !== 0 - ) { - const splitEvents = splitMultiDayEvents(event, this.options.timeZone); + const eventStartDate = + event.startDate instanceof Temporal.PlainDateTime + ? event.startDate.toZonedDateTime(this.options.timeZone) + : event.startDate + const eventEndDate = + event.endDate instanceof Temporal.PlainDateTime + ? event.endDate.toZonedDateTime(this.options.timeZone) + : event.endDate + if (Temporal.ZonedDateTime.compare(eventStartDate, eventEndDate) !== 0) { + const splitEvents = splitMultiDayEvents( + event, + this.options.timeZone, + ) splitEvents.forEach((splitEvent) => { - const splitKey = splitEvent.startDate.toString().split('T')[0]; + const splitKey = splitEvent.startDate.toString().split('T')[0] if (splitKey) { - if (!map.has(splitKey)) map.set(splitKey, []); - map.get(splitKey)?.push(splitEvent); + if (!map.has(splitKey)) map.set(splitKey, []) + map.get(splitKey)?.push(splitEvent) } - }); + }) } else { - const eventKey = event.startDate.toString().split('T')[0]; + const eventKey = event.startDate.toString().split('T')[0] if (eventKey) { - if (!map.has(eventKey)) map.set(eventKey, []); - map.get(eventKey)?.push(event); + if (!map.has(eventKey)) map.set(eventKey, []) + map.get(eventKey)?.push(event) } } - }); - return map; + }) + return map } getDaysWithEvents() { - const calendarDays = this.getCalendarDays(); - const eventMap = this.getEventMap(); + const calendarDays = this.getCalendarDays() + const eventMap = this.getEventMap() return calendarDays.map((day) => { - const dayKey = day.toString(); - const dailyEvents = eventMap.get(dayKey) ?? []; + const dayKey = day.toString() + const dailyEvents = eventMap.get(dayKey) ?? [] const currentMonthRange = Array.from( { length: this.store.state.viewMode.value }, (_, i) => this.store.state.currentPeriod.add({ months: i }).month, - ); - const isInCurrentPeriod = currentMonthRange.includes(day.month); + ) + const isInCurrentPeriod = currentMonthRange.includes(day.month) return { date: day, events: dailyEvents, isToday: Temporal.PlainDate.compare(day, Temporal.Now.plainDateISO()) === 0, isInCurrentPeriod, - }; - }); + } + }) } getDaysNames() { - const baseDate = Temporal.PlainDate.from('2024-01-01'); + const baseDate = Temporal.PlainDate.from('2024-01-01') return Array.from({ length: 7 }).map((_, i) => baseDate .add({ days: (i + (this.getFirstDayOfWeek().dayOfWeek + 1)) % 7 }) .toLocaleString(this.options.locale, { weekday: 'short' }), - ); + ) } - changeViewMode(newViewMode: CalendarState['viewMode']) { + changeViewMode(newViewMode: CalendarStore['viewMode']) { this.store.setState((prev) => ({ ...prev, viewMode: newViewMode, - })); + })) } goToPreviousPeriod() { - const firstDayOfMonth = this.getFirstDayOfMonth(); - const firstDayOfWeek = this.getFirstDayOfWeek(); + const firstDayOfMonth = this.getFirstDayOfMonth() + const firstDayOfWeek = this.getFirstDayOfWeek() switch (this.store.state.viewMode.unit) { case 'month': { - const firstDayOfPrevMonth = firstDayOfMonth.subtract({ months: this.store.state.viewMode.value }); + const firstDayOfPrevMonth = firstDayOfMonth.subtract({ + months: this.store.state.viewMode.value, + }) this.store.setState((prev) => ({ ...prev, currentPeriod: firstDayOfPrevMonth, - })); - break; + })) + break } case 'week': { - const firstDayOfPrevWeek = firstDayOfWeek.subtract({ weeks: this.store.state.viewMode.value }); + const firstDayOfPrevWeek = firstDayOfWeek.subtract({ + weeks: this.store.state.viewMode.value, + }) this.store.setState((prev) => ({ ...prev, currentPeriod: firstDayOfPrevWeek, - })); - break; + })) + break } case 'day': { - const prevCustomStart = this.store.state.currentPeriod.subtract({ days: this.store.state.viewMode.value }); + const prevCustomStart = this.store.state.currentPeriod.subtract({ + days: this.store.state.viewMode.value, + }) this.store.setState((prev) => ({ ...prev, currentPeriod: prevCustomStart, - })); - break; + })) + break } } } goToNextPeriod() { - const firstDayOfMonth = this.getFirstDayOfMonth(); - const firstDayOfWeek = this.getFirstDayOfWeek(); + const firstDayOfMonth = this.getFirstDayOfMonth() + const firstDayOfWeek = this.getFirstDayOfWeek() switch (this.store.state.viewMode.unit) { case 'month': { - const firstDayOfNextMonth = firstDayOfMonth.add({ months: this.store.state.viewMode.value }); + const firstDayOfNextMonth = firstDayOfMonth.add({ + months: this.store.state.viewMode.value, + }) this.store.setState((prev) => ({ ...prev, currentPeriod: firstDayOfNextMonth, - })); - break; + })) + break } case 'week': { - const firstDayOfNextWeek = firstDayOfWeek.add({ weeks: this.store.state.viewMode.value }); + const firstDayOfNextWeek = firstDayOfWeek.add({ + weeks: this.store.state.viewMode.value, + }) this.store.setState((prev) => ({ ...prev, currentPeriod: firstDayOfNextWeek, - })); - break; + })) + break } case 'day': { - const nextCustomStart = this.store.state.currentPeriod.add({ days: this.store.state.viewMode.value }); + const nextCustomStart = this.store.state.currentPeriod.add({ + days: this.store.state.viewMode.value, + }) this.store.setState((prev) => ({ ...prev, currentPeriod: nextCustomStart, - })); - break; + })) + break } } } @@ -295,31 +340,34 @@ export class CalendarCore { this.store.setState((prev) => ({ ...prev, currentPeriod: Temporal.Now.plainDateISO(), - })); + })) } goToSpecificPeriod(date: Temporal.PlainDate) { this.store.setState((prev) => ({ ...prev, currentPeriod: date, - })); + })) } updateCurrentTime() { this.store.setState((prev) => ({ ...prev, currentTime: Temporal.Now.plainDateTimeISO(), - })); + })) } getEventProps(id: Event['id']) { - return getEventProps(this.getEventMap(), id, this.store.state); + return getEventProps(this.getEventMap(), id, this.store.state) } - getCurrentTimeMarkerProps(): { style: CSSProperties; currentTime: string | undefined } { - const { hour, minute } = this.store.state.currentTime; - const currentTimeInMinutes = hour * 60 + minute; - const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100; + getCurrentTimeMarkerProps(): { + style: CSSProperties + currentTime: string | undefined + } { + const { hour, minute } = this.store.state.currentTime + const currentTimeInMinutes = hour * 60 + minute + const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 return { style: { @@ -327,11 +375,23 @@ export class CalendarCore { top: `${percentageOfDay}%`, left: 0, }, - currentTime: this.store.state.currentTime.toString().split('T')[1]?.substring(0, 5), - }; + currentTime: this.store.state.currentTime + .toString() + .split('T')[1] + ?.substring(0, 5), + } } - groupDaysBy({ days, unit, fillMissingDays = true }: Omit, 'weekStartsOn'>) { - return groupDaysBy({ days, unit, fillMissingDays, weekStartsOn: this.getFirstDayOfWeek().dayOfWeek } as GroupDaysByProps); + groupDaysBy({ + days, + unit, + fillMissingDays = true, + }: Omit, 'weekStartsOn'>) { + return groupDaysBy({ + days, + unit, + fillMissingDays, + weekStartsOn: this.getFirstDayOfWeek().dayOfWeek, + } as GroupDaysByProps) } } From 4165b202724c3204a52c3a722b4f668a367ffdb7 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 25 Jun 2024 12:05:25 +0200 Subject: [PATCH 087/128] refactor: types --- packages/react-time/src/useCalendar/useCalendar.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 6dd3412..a9db19d 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -67,9 +67,7 @@ export const useCalendar = ( const groupDaysBy = useCallback((props) => calendarCore.groupDaysBy(props), [calendarCore]) return { - currentPeriod: state.currentPeriod, - viewMode: state.viewMode, - currentTime: state.currentTime, + ...state, days: calendarCore.getDaysWithEvents(), daysNames: calendarCore.getDaysNames(), goToPreviousPeriod, From 1b71c7eb681c59e159320f28ebd664c9ffff4004 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 25 Jun 2024 14:00:21 +0200 Subject: [PATCH 088/128] test: useCalendar --- .../react-time/src/tests/useCalendar.test.tsx | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index ea50fed..ba20a20 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -241,20 +241,13 @@ describe('useCalendar', () => { expect(weeks.find((week) => week.some((day) => day?.isToday))?.find((day) => day?.isToday)?.date.toString()).toBe('2024-06-01'); }); - test('should return the correct day names based on weekStartsOn', () => { + test('should return the correct day names based on the locale', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US', weekStartsOn: 1 }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) ); const { daysNames } = result.current; expect(daysNames).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']); - - const { result: resultSundayStart } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US', weekStartsOn: 7 }) - ); - - const { daysNames: sundayDaysNames } = resultSundayStart.current; - expect(sundayDaysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']); }); test('should correctly mark days as in current period', () => { @@ -355,17 +348,4 @@ describe('useCalendar', () => { expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-27'); expect(weeks[4]?.[6]?.date.toString()).toBe('2024-06-30'); }); - - test('should group days by weeks correctly when weekStartsOn is Sunday', () => { - const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US', weekStartsOn: 7 }) - ); - - const { days, groupDaysBy } = result.current; - const weeks = groupDaysBy({ days, unit: 'week' }); - - expect(weeks).toHaveLength(6); - expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-26'); - expect(weeks[4]?.[6]?.date.toString()).toBe('2024-06-29'); - }); }); From 604c8bb7f611ccdada6b6a951600793d1bfd0a5f Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 25 Jun 2024 16:41:18 +0200 Subject: [PATCH 089/128] refactor: useIsomorphicLayoutEffect --- packages/react-time/src/useCalendar/useCalendar.ts | 5 +++-- packages/react-time/src/utils/index.ts | 1 + packages/react-time/src/utils/useIsomorphicLayoutEffect.ts | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 packages/react-time/src/utils/index.ts create mode 100644 packages/react-time/src/utils/useIsomorphicLayoutEffect.ts diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index a9db19d..84a236c 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,7 +1,8 @@ -import { useCallback, useEffect, useRef, useState, useTransition } from 'react' +import { useCallback, useRef, useState, useTransition } from 'react' import { useStore } from '@tanstack/react-store' import { Temporal } from '@js-temporal/polyfill' import { CalendarCore, type Event } from '@tanstack/time' +import { useIsomorphicLayoutEffect } from '../utils' import type { CalendarApi, CalendarCoreOptions } from '@tanstack/time' export const useCalendar = ( @@ -16,7 +17,7 @@ export const useCalendar = ( calendarCore.updateCurrentTime() }, [calendarCore]) - useEffect(() => { + useIsomorphicLayoutEffect(() => { if (currentTimeInterval.current) clearTimeout(currentTimeInterval.current) const now = Temporal.Now.plainDateTimeISO() diff --git a/packages/react-time/src/utils/index.ts b/packages/react-time/src/utils/index.ts new file mode 100644 index 0000000..881ccca --- /dev/null +++ b/packages/react-time/src/utils/index.ts @@ -0,0 +1 @@ +export * from './useIsomorphicLayoutEffect' diff --git a/packages/react-time/src/utils/useIsomorphicLayoutEffect.ts b/packages/react-time/src/utils/useIsomorphicLayoutEffect.ts new file mode 100644 index 0000000..287b88a --- /dev/null +++ b/packages/react-time/src/utils/useIsomorphicLayoutEffect.ts @@ -0,0 +1,4 @@ +import { useEffect, useLayoutEffect } from "react"; + +export const useIsomorphicLayoutEffect = + typeof window !== 'undefined' ? useLayoutEffect : useEffect \ No newline at end of file From 63111957dba75d5a3bec0d0c8a1aed45e0cc231a Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 26 Jun 2024 23:26:34 +0200 Subject: [PATCH 090/128] docs: calendar --- docs/framework/react/reference/useCalendar.md | 74 +++++++++---------- docs/reference/calendar-core.md | 39 +++++----- 2 files changed, 55 insertions(+), 58 deletions(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index 0582268..81ca29a 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -20,56 +20,48 @@ export function useCalendar({ #### Parameters -- `weekStartsOn?: number` - - This parameter is an optional number that specifies the day of the week that the calendar should start on. It defaults to 0, which is Sunday. -- `events: Event[]` - - This parameter is an array of events that the calendar should display. -- `viewMode: 'month' | 'week' | number` - - This parameter is a string that specifies the initial view mode of the calendar. It can be either 'month', 'week', or a number representing the number of days in a custom view mode. -- `locale?: string` - - This parameter is an optional string that specifies the locale to use for formatting dates and times. It defaults to the system locale. -- `onChangeViewMode?: ({ value: number; unit: "month" | "week" | "day"; }) => void` - - This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. -- `onChangeViewMode?: (viewMode: value: number; unit: "month" | "week" | "day";) => void` - - This parameter is an optional callback function that is called when the view mode of the calendar changes. It receives the new view mode as an argument. -- `reducer?: (state: CalendarState, action: CalendarAction) => CalendarState` - - This parameter is an optional custom reducer function that can be used to manage the state of the calendar. +- `events?: TEvent[] | null` +An optional array of events to be handled by the calendar. +- `viewMode: CalendarStore['viewMode']` +The initial view mode configuration of the calendar. +- `locale?: Intl.UnicodeBCP47LocaleIdentifier` +Optional locale for date formatting. Uses a BCP 47 language tag. +- `timeZone?: Temporal.TimeZoneLike` +Optional time zone specification for the calendar. +- `calendar?: Temporal.CalendarLike` +Optional calendar system to be used. #### Returns -- `firstDayOfPeriod: Temporal.PlainDate` - - This value represents the first day of the current period displayed by the calendar. -- `currentPeriod: string` - - This value represents a string that describes the current period displayed by the calendar. -- `goToPreviousPeriod: MouseEventHandler` - - This function is a click event handler that navigates to the previous period. -- `goToNextPeriod: MouseEventHandler` - - This function is a click event handler that navigates to the next period. -- `goToCurrentPeriod: MouseEventHandler` - - This function is a click event handler that navigates to the current period. +`firstDayOfPeriod: Temporal.PlainDate` +This value represents the first day of the current period displayed by the calendar. +`currentPeriod: string` +This value represents a string that describes the current period displayed by the calendar. +`goToPreviousPeriod: () => void` +This function navigates to the previous period. +`goToNextPeriod: () => void` +This function navigates to the next period. +- `goToCurrentPeriod: () => void` +This function navigates to the current period. - `goToSpecificPeriod: (date: Temporal.PlainDate) => void` - - This function is a callback function that is called when a date is selected in the calendar. It receives the selected date as an argument. +This function navigates to a specific period based on the provided date. - `days: Day[]` - - This value represents an array of days in the current period displayed by the calendar. +This value represents an array of days in the current period displayed by the calendar. - `daysNames: string[]` - - This value represents an array of strings that contain the names of the days of the week. -- `viewMode: 'month' | 'week' | number` - - This value represents the current view mode of the calendar. -- `changeViewMode: (newViewMode: 'month' | 'week' | number) => void` - - This function is used to change the view mode of the calendar. +This value represents an array of strings that contain the names of the days of the week. +- `viewMode: CalendarStore['viewMode']` +This value represents the current view mode of the calendar. +- `changeViewMode: (newViewMode: CalendarStore['viewMode']) => void` +This function is used to change the view mode of the calendar. - `getEventProps: (id: string) => { style: CSSProperties } | null` - - This function is used to retrieve the style properties for a specific event based on its ID. -- `getEventProps: (id: string) => { style: CSSProperties } | null` - - This function is used to retrieve the style properties for a specific event based on its ID. -- `getEventProps: (id: string) => { style: CSSProperties } | null` - - This function is used to retrieve the style properties for a specific event based on its ID. -- `getCurrentTimeMarkerProps: () => { style: CSSProperties, currentTime: Temporal.PlainTime }` - - This function is used to retrieve the style properties and current time for the current time marker. +This function is used to retrieve the style properties for a specific event based on its ID. +- `getCurrentTimeMarkerProps: () => { style: CSSProperties, currentTime: string | undefined }` +This function is used to retrieve the style properties and current time for the current time marker. - `isPending: boolean` - - This value represents whether the calendar is in a pending state. -- `groupDaysBy: ({ days: Day[], unit: 'week' | 'month', fillMissingDays?: boolean }) => Day[][]` - - This function is used to group the days in the current period by a specified unit. The `fillMissingDays` parameter can be used to fill in missing days with previous or next month's days. +This value represents whether the calendar is in a pending state. +- `groupDaysBy: (props: Omit, 'weekStartsOn'>) => (Day | null)[][]` +This function is used to group the days in the current period by a specified unit. The fillMissingDays parameter can be used to fill in missing days with previous or next month's days. #### Example Usage diff --git a/docs/reference/calendar-core.md b/docs/reference/calendar-core.md index 22e4b52..8d758c5 100644 --- a/docs/reference/calendar-core.md +++ b/docs/reference/calendar-core.md @@ -17,39 +17,43 @@ The `CalendarCore` class provides a set of functionalities for managing calendar #### Parameters - `weekStartsOn?: number` - - An optional number that specifies the day of the week that the calendar should start on. It defaults to 1 (Monday). +An optional number that specifies the day of the week that the calendar should start on. It defaults to 1 (Monday). - `events?: TEvent[]` - - An optional array of events that the calendar should display. +An optional array of events that the calendar should display. - `viewMode: ViewMode` - - An object that specifies the initial view mode of the calendar. +An object that specifies the initial view mode of the calendar. - `locale?: Parameters['0']` - - An optional string that specifies the locale to use for formatting dates and times. +An optional string that specifies the locale to use for formatting dates and times. +- `timeZone?: Temporal.TimeZoneLike` +Optional time zone specification for the calendar. +- `calendar?: Temporal.CalendarLike` +Optional calendar system to be used. #### Returns - `getDaysWithEvents(): Array>` - - Returns an array of days in the current period with their associated events. +Returns an array of days in the current period with their associated events. - `getDaysNames(): string[]` - - Returns an array of strings representing the names of the days of the week based on the locale and week start day. -- `changeViewMode(newViewMode: ViewMode): void` - - Changes the view mode of the calendar. +Returns an array of strings representing the names of the days of the week based on the locale and week start day. +- `changeViewMode(newViewMode: CalendarStore['viewMode']): void` +Changes the view mode of the calendar. - `goToPreviousPeriod(): void` - - Navigates to the previous period based on the current view mode. +Navigates to the previous period based on the current view mode. - `goToNextPeriod(): void` - - Navigates to the next period based on the current view mode. +Navigates to the next period based on the current view mode. - `goToCurrentPeriod(): void` - - Navigates to the current period. +Navigates to the current period. - `goToSpecificPeriod(date: Temporal.PlainDate): void` - - Navigates to a specific period based on the provided date. +Navigates to a specific period based on the provided date. - `updateCurrentTime(): void` - - Updates the current time. +Updates the current time. - `getEventProps(id: Event['id']): { style: CSSProperties } | null` - - Retrieves the style properties for a specific event based on its ID. +Retrieves the style properties for a specific event based on its ID. - `getCurrentTimeMarkerProps(): { style: CSSProperties; currentTime: string | undefined }` - - Retrieves the style properties and current time for the current time marker. +Retrieves the style properties and current time for the current time marker. - `groupDaysBy(props: Omit, 'weekStartsOn'>): (Day | null)[][]` - - Groups the days in the current period by a specified unit. The fillMissingDays parameter can be used to fill in missing days with previous or next month's days. +Groups the days in the current period by a specified unit. The fillMissingDays parameter can be used to fill in missing days with previous or next month's days. #### Example Usage @@ -80,10 +84,11 @@ const events: MyEvent[] = [ ]; const calendarCore = new CalendarCore({ - weekStartsOn: 1, viewMode: { value: 1, unit: 'month' }, events, locale: 'en-US', + timeZone: 'America/New_York', + calendar: 'gregory', }); // Get days with events From 6627c0c1dfe67dce45714eb2e4ff2fd78f0dc2ed Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 26 Jun 2024 23:28:53 +0200 Subject: [PATCH 091/128] docs: calendar --- docs/framework/react/reference/useCalendar.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/framework/react/reference/useCalendar.md b/docs/framework/react/reference/useCalendar.md index 81ca29a..42ad7fa 100644 --- a/docs/framework/react/reference/useCalendar.md +++ b/docs/framework/react/reference/useCalendar.md @@ -82,10 +82,11 @@ const CalendarComponent = ({ events }) => { getCurrentTimeMarkerProps, groupDaysBy, } = useCalendar({ - weekStartsOn: 1, viewMode: { value: 1, unit: 'month' }, + events, locale: 'en-US', - onChangeViewMode: (newViewMode) => console.log('View mode changed:', newViewMode), + timeZone: 'America/New_York', + calendar: 'gregory', }); return ( From df925d41f7831503d2cbd1bed3f5ad675777bd9d Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Fri, 28 Jun 2024 21:03:29 +0200 Subject: [PATCH 092/128] refactor: zonedDateTime --- packages/time/src/calendar/getEventProps.ts | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/time/src/calendar/getEventProps.ts b/packages/time/src/calendar/getEventProps.ts index 023d59b..8e94945 100644 --- a/packages/time/src/calendar/getEventProps.ts +++ b/packages/time/src/calendar/getEventProps.ts @@ -1,17 +1,17 @@ import { Temporal } from "@js-temporal/polyfill"; import type { Properties as CSSProperties } from "csstype"; -import type { CalendarState, Event } from "./types"; +import type { CalendarStore, Event } from "./types"; export const getEventProps = ( eventMap: Map, id: Event['id'], - state: CalendarState + state: CalendarStore, ): { style: CSSProperties } | null => { const event = [...eventMap.values()].flat().find((currEvent) => currEvent.id === id); if (!event) return null; - const eventStartDate = Temporal.PlainDateTime.from(event.startDate); - const eventEndDate = Temporal.PlainDateTime.from(event.endDate); + const eventStartDate = Temporal.ZonedDateTime.from(event.startDate); + const eventEndDate = Temporal.ZonedDateTime.from(event.endDate); const isSplitEvent = Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0; let percentageOfDay; @@ -37,18 +37,18 @@ export const getEventProps = ( const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20); const overlappingEvents = [...eventMap.values()].flat().filter((e) => { - const eStartDate = Temporal.PlainDateTime.from(e.startDate); - const eEndDate = Temporal.PlainDateTime.from(e.endDate); + const eStartDate = Temporal.ZonedDateTime.from(e.startDate); + const eEndDate = Temporal.ZonedDateTime.from(e.endDate); return ( (e.id !== id && - Temporal.PlainDateTime.compare(eventStartDate, eStartDate) >= 0 && - Temporal.PlainDateTime.compare(eventStartDate, eEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eventEndDate, eStartDate) >= 0 && - Temporal.PlainDateTime.compare(eventEndDate, eEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eStartDate, eventStartDate) >= 0 && - Temporal.PlainDateTime.compare(eStartDate, eventEndDate) <= 0) || - (Temporal.PlainDateTime.compare(eEndDate, eventStartDate) >= 0 && - Temporal.PlainDateTime.compare(eEndDate, eventEndDate) <= 0) + Temporal.ZonedDateTime.compare(eventStartDate, eStartDate) >= 0 && + Temporal.ZonedDateTime.compare(eventStartDate, eEndDate) <= 0) || + (Temporal.ZonedDateTime.compare(eventEndDate, eStartDate) >= 0 && + Temporal.ZonedDateTime.compare(eventEndDate, eEndDate) <= 0) || + (Temporal.ZonedDateTime.compare(eStartDate, eventStartDate) >= 0 && + Temporal.ZonedDateTime.compare(eStartDate, eventEndDate) <= 0) || + (Temporal.ZonedDateTime.compare(eEndDate, eventStartDate) >= 0 && + Temporal.ZonedDateTime.compare(eEndDate, eventEndDate) <= 0) ); }); From c65b760308a9cf0a401a65cab4292d6244e4d6c2 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Fri, 28 Jun 2024 21:21:53 +0200 Subject: [PATCH 093/128] refactor: rename startDate to start and endDate to end --- packages/time/src/calendar/getEventProps.ts | 8 ++++---- .../time/src/calendar/splitMultiDayEvents.ts | 6 +++--- packages/time/src/calendar/types.ts | 4 ++-- packages/time/src/core/calendar.ts | 16 ++++++++-------- packages/time/src/tests/calendar-core.test.ts | 8 ++++---- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/time/src/calendar/getEventProps.ts b/packages/time/src/calendar/getEventProps.ts index 8e94945..cd6ce5e 100644 --- a/packages/time/src/calendar/getEventProps.ts +++ b/packages/time/src/calendar/getEventProps.ts @@ -10,8 +10,8 @@ export const getEventProps = ( const event = [...eventMap.values()].flat().find((currEvent) => currEvent.id === id); if (!event) return null; - const eventStartDate = Temporal.ZonedDateTime.from(event.startDate); - const eventEndDate = Temporal.ZonedDateTime.from(event.endDate); + const eventStartDate = Temporal.ZonedDateTime.from(event.start); + const eventEndDate = Temporal.ZonedDateTime.from(event.end); const isSplitEvent = Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0; let percentageOfDay; @@ -37,8 +37,8 @@ export const getEventProps = ( const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20); const overlappingEvents = [...eventMap.values()].flat().filter((e) => { - const eStartDate = Temporal.ZonedDateTime.from(e.startDate); - const eEndDate = Temporal.ZonedDateTime.from(e.endDate); + const eStartDate = Temporal.ZonedDateTime.from(e.start); + const eEndDate = Temporal.ZonedDateTime.from(e.end); return ( (e.id !== id && Temporal.ZonedDateTime.compare(eventStartDate, eStartDate) >= 0 && diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts index 0d2088d..4fb1560 100644 --- a/packages/time/src/calendar/splitMultiDayEvents.ts +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -2,8 +2,8 @@ import { Temporal } from '@js-temporal/polyfill'; import type { Event } from './types'; export const splitMultiDayEvents = (event: TEvent, timeZone: Temporal.TimeZoneLike): TEvent[] => { - const startDate = event.startDate instanceof Temporal.PlainDateTime ? event.startDate.toZonedDateTime(timeZone) : event.startDate; - const endDate = event.endDate instanceof Temporal.PlainDateTime ? event.endDate.toZonedDateTime(timeZone) : event.endDate; + const startDate = event.start instanceof Temporal.PlainDateTime ? event.start.toZonedDateTime(timeZone) : event.start; + const endDate = event.end instanceof Temporal.PlainDateTime ? event.end.toZonedDateTime(timeZone) : event.end; const events: TEvent[] = []; let currentDay = startDate; @@ -14,7 +14,7 @@ export const splitMultiDayEvents = (event: TEvent, timeZon const eventStart = Temporal.PlainDateTime.compare(currentDay, startDate) === 0 ? startDate : startOfCurrentDay; const eventEnd = Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 ? endDate : endOfCurrentDay; - events.push({ ...event, startDate: eventStart, endDate: eventEnd }); + events.push({ ...event, start: eventStart, end: eventEnd }); currentDay = startOfCurrentDay.add({ days: 1 }); } diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index 08ffe7e..4341d4b 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -2,8 +2,8 @@ import type { Temporal } from "@js-temporal/polyfill" export interface Event { id: string; - startDate: Temporal.PlainDateTime | Temporal.ZonedDateTime; - endDate: Temporal.PlainDateTime | Temporal.ZonedDateTime; + start: Temporal.PlainDateTime | Temporal.ZonedDateTime; + end: Temporal.PlainDateTime | Temporal.ZonedDateTime; title: string; } diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 50fe6b8..2c21986 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -189,27 +189,27 @@ export class CalendarCore const map = new Map() this.options.events?.forEach((event) => { const eventStartDate = - event.startDate instanceof Temporal.PlainDateTime - ? event.startDate.toZonedDateTime(this.options.timeZone) - : event.startDate + event.start instanceof Temporal.PlainDateTime + ? event.start.toZonedDateTime(this.options.timeZone) + : event.start const eventEndDate = - event.endDate instanceof Temporal.PlainDateTime - ? event.endDate.toZonedDateTime(this.options.timeZone) - : event.endDate + event.end instanceof Temporal.PlainDateTime + ? event.end.toZonedDateTime(this.options.timeZone) + : event.end if (Temporal.ZonedDateTime.compare(eventStartDate, eventEndDate) !== 0) { const splitEvents = splitMultiDayEvents( event, this.options.timeZone, ) splitEvents.forEach((splitEvent) => { - const splitKey = splitEvent.startDate.toString().split('T')[0] + const splitKey = splitEvent.start.toString().split('T')[0] if (splitKey) { if (!map.has(splitKey)) map.set(splitKey, []) map.get(splitKey)?.push(splitEvent) } }) } else { - const eventKey = event.startDate.toString().split('T')[0] + const eventKey = event.start.toString().split('T')[0] if (eventKey) { if (!map.has(eventKey)) map.set(eventKey, []) map.get(eventKey)?.push(event) diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 22f6697..82d896f 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -16,14 +16,14 @@ describe('CalendarCore', () => { events: [ { id: '1', - startDate: Temporal.PlainDateTime.from('2023-06-10T09:00'), - endDate: Temporal.PlainDateTime.from('2023-06-10T10:00'), + start: Temporal.PlainDateTime.from('2023-06-10T09:00'), + end: Temporal.PlainDateTime.from('2023-06-10T10:00'), title: 'Event 1', }, { id: '2', - startDate: Temporal.PlainDateTime.from('2023-06-12T11:00'), - endDate: Temporal.PlainDateTime.from('2023-06-12T12:00'), + start: Temporal.PlainDateTime.from('2023-06-12T11:00'), + end: Temporal.PlainDateTime.from('2023-06-12T12:00'), title: 'Event 2', }, ], From 18ad00c36066c1847c8dd7ac6a7a8458be2dacb1 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Fri, 28 Jun 2024 21:59:30 +0200 Subject: [PATCH 094/128] refactor: remove the getCurrentTimeMarkerProps method --- .../react-time/src/tests/useCalendar.test.tsx | 96 ++----------------- .../react-time/src/useCalendar/useCalendar.ts | 26 +---- packages/time/src/calendar/types.ts | 1 - packages/time/src/core/calendar.ts | 36 ------- packages/time/src/tests/calendar-core.test.ts | 41 -------- 5 files changed, 11 insertions(+), 189 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index ba20a20..2159dd8 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -1,20 +1,20 @@ import { Temporal } from '@js-temporal/polyfill' -import { describe, expect, test, vi } from 'vitest' -import { act, renderHook, waitFor } from '@testing-library/react' +import { describe, expect, test } from 'vitest' +import { act, renderHook } from '@testing-library/react' import { useCalendar } from '../useCalendar' describe('useCalendar', () => { const events = [ { id: '1', - startDate: Temporal.PlainDateTime.from('2024-06-01T10:00:00'), - endDate: Temporal.PlainDateTime.from('2024-06-01T12:00:00'), + start: Temporal.PlainDateTime.from('2024-06-01T10:00:00'), + end: Temporal.PlainDateTime.from('2024-06-01T12:00:00'), title: 'Event 1', }, { id: '2', - startDate: Temporal.PlainDateTime.from('2024-06-02T14:00:00'), - endDate: Temporal.PlainDateTime.from('2024-06-02T16:00:00'), + start: Temporal.PlainDateTime.from('2024-06-02T14:00:00'), + end: Temporal.PlainDateTime.from('2024-06-02T16:00:00'), title: 'Event 2', }, ] @@ -108,14 +108,14 @@ describe('useCalendar', () => { const overlappingEvents = [ { id: '1', - startDate: Temporal.PlainDateTime.from('2024-06-01T10:00:00'), - endDate: Temporal.PlainDateTime.from('2024-06-01T12:00:00'), + start: Temporal.PlainDateTime.from('2024-06-01T10:00:00'), + end: Temporal.PlainDateTime.from('2024-06-01T12:00:00'), title: 'Event 1', }, { id: '2', - startDate: Temporal.PlainDateTime.from('2024-06-01T11:00:00'), - endDate: Temporal.PlainDateTime.from('2024-06-01T13:00:00'), + start: Temporal.PlainDateTime.from('2024-06-01T11:00:00'), + end: Temporal.PlainDateTime.from('2024-06-01T13:00:00'), title: 'Event 2', }, ] @@ -149,82 +149,6 @@ describe('useCalendar', () => { }) }) - test('should return the correct props for the current time marker', () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date('2024-06-01T11:00:00')); - const { result } = renderHook(() => - useCalendar({ viewMode: { value: 1, unit: 'week' } }), - ); - - const getCurrentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); - - expect(getCurrentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.83333333333333%', - left: 0, - }, - currentTime: '11:00', - }); - }); - - test('should update the current time marker props after time passes', () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date('2024-06-01T11:00:00')); - const { result } = renderHook(() => - useCalendar({ viewMode: { value: 1, unit: 'week' } }), - ); - - const currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); - - expect(currentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.83333333333333%', - left: 0, - }, - currentTime: '11:00', - }); - - act(() => { - vi.advanceTimersByTime(60000); - }); - - waitFor(() => { - expect(currentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.90277777777778%', - left: 0, - }, - currentTime: '11:01', - }); - }); - }); - - test('should update the current time marker props after time passes when the next minute is in less than a minute', () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date('2024-06-01T11:00:55')); - - const { result } = renderHook(() => useCalendar({ viewMode: { value: 1, unit: 'week' } })); - const currentTimeMarkerProps = result.current.getCurrentTimeMarkerProps(); - - act(() => { - vi.advanceTimersByTime(5000); - }) - - waitFor(() => { - expect(currentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.90277777777778%', - left: 0, - }, - currentTime: '11:01', - }); - }) - }); - test('should render array of days', () => { const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }), diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 84a236c..8dd5ea7 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,8 +1,6 @@ -import { useCallback, useRef, useState, useTransition } from 'react' +import { useCallback, useState, useTransition } from 'react' import { useStore } from '@tanstack/react-store' -import { Temporal } from '@js-temporal/polyfill' import { CalendarCore, type Event } from '@tanstack/time' -import { useIsomorphicLayoutEffect } from '../utils' import type { CalendarApi, CalendarCoreOptions } from '@tanstack/time' export const useCalendar = ( @@ -11,25 +9,6 @@ export const useCalendar = ( const [calendarCore] = useState(() => new CalendarCore(options)) const state = useStore(calendarCore.store) const [isPending, startTransition] = useTransition() - const currentTimeInterval = useRef() - - const updateCurrentTime = useCallback(() => { - calendarCore.updateCurrentTime() - }, [calendarCore]) - - useIsomorphicLayoutEffect(() => { - if (currentTimeInterval.current) clearTimeout(currentTimeInterval.current) - - const now = Temporal.Now.plainDateTimeISO() - const msToNextMinute = (60 - now.second) * 1000 - now.millisecond - - currentTimeInterval.current = setTimeout(() => { - updateCurrentTime() - currentTimeInterval.current = setInterval(updateCurrentTime, 60000) - }, msToNextMinute) - - return () => clearTimeout(currentTimeInterval.current) - }, [calendarCore, updateCurrentTime]) const goToPreviousPeriod = useCallback(() => { startTransition(() => { @@ -63,8 +42,6 @@ export const useCalendar = ( const getEventProps = useCallback((id) => calendarCore.getEventProps(id), [calendarCore]) - const getCurrentTimeMarkerProps = useCallback(() => calendarCore.getCurrentTimeMarkerProps(), [calendarCore]) - const groupDaysBy = useCallback((props) => calendarCore.groupDaysBy(props), [calendarCore]) return { @@ -77,7 +54,6 @@ export const useCalendar = ( goToSpecificPeriod, changeViewMode, getEventProps, - getCurrentTimeMarkerProps, isPending, groupDaysBy, } diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index 4341d4b..4b637de 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -13,7 +13,6 @@ export interface CalendarStore { value: number unit: 'month' | 'week' | 'day' } - currentTime: Temporal.PlainDateTime } export type Day = { diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 2c21986..eb3ca01 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -61,11 +61,6 @@ interface CalendarActions { changeViewMode: (newViewMode: CalendarStore['viewMode']) => void /** Retrieves styling properties for a specific event, identified by ID. */ getEventProps: (id: Event['id']) => { style: CSSProperties } | null - /** Provides properties for the marker indicating the current time. */ - getCurrentTimeMarkerProps: () => { - style: CSSProperties - currentTime: string | undefined - } /** Groups days by a specified unit. */ groupDaysBy: ( props: Omit, 'weekStartsOn'>, @@ -77,8 +72,6 @@ interface CalendarState { currentPeriod: CalendarStore['currentPeriod'] /** The current view mode of the calendar. */ viewMode: CalendarStore['viewMode'] - /** The current date and time according to the calendar's time zone. */ - currentTime: CalendarStore['currentTime'] /** An array of days, each potentially containing events. */ days: Array> /** An array of names for the days of the week, localized to the calendar's locale. */ @@ -115,7 +108,6 @@ export class CalendarCore this.options.calendar, ), viewMode: options.viewMode, - currentTime: Temporal.Now.plainDateTimeISO(this.options.timeZone), }) } @@ -350,38 +342,10 @@ export class CalendarCore })) } - updateCurrentTime() { - this.store.setState((prev) => ({ - ...prev, - currentTime: Temporal.Now.plainDateTimeISO(), - })) - } - getEventProps(id: Event['id']) { return getEventProps(this.getEventMap(), id, this.store.state) } - getCurrentTimeMarkerProps(): { - style: CSSProperties - currentTime: string | undefined - } { - const { hour, minute } = this.store.state.currentTime - const currentTimeInMinutes = hour * 60 + minute - const percentageOfDay = (currentTimeInMinutes / (24 * 60)) * 100 - - return { - style: { - position: 'absolute', - top: `${percentageOfDay}%`, - left: 0, - }, - currentTime: this.store.state.currentTime - .toString() - .split('T')[1] - ?.substring(0, 5), - } - } - groupDaysBy({ days, unit, diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 82d896f..c12f74b 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -87,47 +87,6 @@ describe('CalendarCore', () => { expect(calendarCore.store.state.currentPeriod).toEqual(specificDate); }); - test('should update current time correctly', () => { - const initialTime = calendarCore.store.state.currentTime; - const newMockDateTime = initialTime.add({ minutes: 1 }); - vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(newMockDateTime); - - calendarCore.updateCurrentTime(); - const updatedTime = calendarCore.store.state.currentTime; - expect(updatedTime).toEqual(newMockDateTime); - }); - - test('should return the correct props for the current time marker', () => { - vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(Temporal.PlainDateTime.from('2024-06-01T11:00:00')); - - const coreOptions: CalendarCoreOptions = { - viewMode: { value: 1, unit: 'week' }, - events: [], - timeZone: mockTimeZone, - }; - calendarCore = new CalendarCore(coreOptions); - - const currentTimeMarkerProps = calendarCore.getCurrentTimeMarkerProps(); - - expect(currentTimeMarkerProps).toEqual({ - style: { - position: 'absolute', - top: '45.83333333333333%', - left: 0, - }, - currentTime: '11:00', - }); - }); - - test('should update the current time', () => { - const initialTime = calendarCore.store.state.currentTime; - const newMockDateTime = initialTime.add({ minutes: 1 }); - vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(newMockDateTime); - - calendarCore.updateCurrentTime(); - expect(calendarCore.store.state.currentTime).toEqual(newMockDateTime); - }); - test('should group days correctly', () => { const daysWithEvents = calendarCore.getDaysWithEvents(); const groupedDays = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'month' }); From 403df9927f46fbc767d1e4bbfb18407dcf0d2ff0 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 30 Jun 2024 20:34:15 +0200 Subject: [PATCH 095/128] refactor: weekInfoPolyfill --- packages/time/src/core/weekInfoPolyfill.ts | 1864 ----------------- .../time/src/core/weekInfoPolyfill/index.ts | 1 + .../src/core/weekInfoPolyfill/weekInfoData.ts | 1832 ++++++++++++++++ .../core/weekInfoPolyfill/weekInfoPolyfill.ts | 41 + packages/time/src/utils/getFirstDayOfWeek.ts | 2 +- 5 files changed, 1875 insertions(+), 1865 deletions(-) delete mode 100644 packages/time/src/core/weekInfoPolyfill.ts create mode 100644 packages/time/src/core/weekInfoPolyfill/index.ts create mode 100644 packages/time/src/core/weekInfoPolyfill/weekInfoData.ts create mode 100644 packages/time/src/core/weekInfoPolyfill/weekInfoPolyfill.ts diff --git a/packages/time/src/core/weekInfoPolyfill.ts b/packages/time/src/core/weekInfoPolyfill.ts deleted file mode 100644 index 28c7351..0000000 --- a/packages/time/src/core/weekInfoPolyfill.ts +++ /dev/null @@ -1,1864 +0,0 @@ -interface WeekInfo { - firstDay: number - weekend: number[] - minimalDays: number -} - -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Intl { - interface Locale { - getWeekInfo: () => WeekInfo - weekInfo?: WeekInfo - } -} - -;(function () { - if (typeof (Intl as any).Locale.prototype.getWeekInfo !== 'function') { - ;(Intl as any).Locale.prototype.getWeekInfo = function () { - const locale = this.toString().toLowerCase() - const weekInfo: Record = { - af: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ak: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - am: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hy: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - as: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - asa: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - az: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bm: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - eu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - be: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bem: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bez: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bs: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - my: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ca: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - tzm: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - chr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cgg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - zh: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cs: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - da: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ebu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - eo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - et: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ee: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fil: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ff: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ka: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - de: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - el: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - guz: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ha: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - haw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - he: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - is: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ig: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - id: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ga: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - it: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ja: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kea: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kab: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kln: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kam: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - km: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ki: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kok: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ko: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - khq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ses: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lag: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lv: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lt: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - luo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - luy: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - jmc: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kde: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ms: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ml: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mt: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gv: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mas: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mer: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mfe: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - naq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ne: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nd: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nb: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nyn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - or: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - om: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ps: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fa: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - pl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - pt: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - pa: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ro: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rm: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rof: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ru: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rwk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - saq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - seh: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ii: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - si: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - xog: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - so: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - es: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sv: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gsw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - shi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - dav: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ta: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - te: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - teo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - th: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ti: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - to: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - tr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - uk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ur: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - uz: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - vi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - vun: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cy: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - yo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - zu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'af-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'am-ET': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-AE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-BH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-DZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-EG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-IQ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-JO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-KW': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-LB': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-LY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-MA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'arn-CL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-OM': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-QA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-SA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-SD': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-SY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-TN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-YE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'as-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'az-az': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'az-Cyrl-AZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'az-Latn-AZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ba-RU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'be-BY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bg-BG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bn-BD': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bn-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bo-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'br-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bs-Cyrl-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bs-Latn-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ca-ES': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'co-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'cs-CZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'cy-GB': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'da-DK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-AT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-CH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-DE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-LI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-LU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'dsb-DE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'dv-MV': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'el-CY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'el-GR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-029': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-AU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-BZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-cb': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-GB': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-IE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-JM': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-MT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-MY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-NZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-PH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-SG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-TT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-US': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-ZW': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-AR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-BO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-CL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-CO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-CR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-DO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-EC': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-ES': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-GT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-HN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-MX': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-NI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-PA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-PE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-PR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-PY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-SV': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-US': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-UY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-VE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'et-EE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'eu-ES': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fa-IR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fi-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fil-PH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fo-FO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-BE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-CH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-LU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-MC': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fy-NL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ga-IE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gd-GB': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gd-ie': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gl-ES': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gsw-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gu-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ha-Latn-NG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'he-IL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hi-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hr-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hr-HR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hsb-DE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hu-HU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hy-AM': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'id-ID': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ig-NG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ii-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'in-ID': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'is-IS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'it-CH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'it-IT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'iu-Cans-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'iu-Latn-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'iw-IL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ja-JP': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ka-GE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'kk-KZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'kl-GL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'km-KH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'kn-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'kok-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ko-KR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ky-KG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'lb-LU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'lo-LA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'lt-LT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'lv-LV': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mi-NZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mk-MK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ml-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mn-MN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mn-Mong-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'moh-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mr-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ms-BN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ms-MY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mt-MT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nb-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ne-NP': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nl-BE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nl-NL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nn-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'no-no': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nso-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'oc-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'or-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'pa-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'pl-PL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'prs-AF': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ps-AF': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'pt-BR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'pt-PT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'qut-GT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'quz-BO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'quz-EC': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'quz-PE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'rm-CH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ro-mo': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ro-RO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ru-mo': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ru-RU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'rw-RW': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sah-RU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sa-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'se-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'se-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'se-SE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'si-LK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sk-SK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sl-SI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sma-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sma-SE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'smj-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'smj-SE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'smn-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sms-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sq-AL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-CS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Cyrl-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Cyrl-CS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Cyrl-ME': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Cyrl-RS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Latn-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Latn-CS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Latn-ME': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Latn-RS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-ME': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-RS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-sp': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sv-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sv-SE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sw-KE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'syr-SY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ta-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'te-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tg-Cyrl-TJ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'th-TH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tk-TM': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tlh-QS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tn-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tr-TR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tt-RU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tzm-Latn-DZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ug-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'uk-UA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ur-PK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'uz-Cyrl-UZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'uz-Latn-UZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'uz-uz': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'vi-VN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'wo-SN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'xh-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'yo-NG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-HK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-MO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-SG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-TW': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zu-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - } - - const match = - weekInfo[locale] || - weekInfo[locale.split('-')[0]] || - weekInfo['default'] - - return { - firstDay: match?.firstDay, - weekend: match?.weekend, - minimalDays: match?.minimalDays, - } - } - } -})() diff --git a/packages/time/src/core/weekInfoPolyfill/index.ts b/packages/time/src/core/weekInfoPolyfill/index.ts new file mode 100644 index 0000000..92eafb3 --- /dev/null +++ b/packages/time/src/core/weekInfoPolyfill/index.ts @@ -0,0 +1 @@ +import './weekInfoPolyfill' diff --git a/packages/time/src/core/weekInfoPolyfill/weekInfoData.ts b/packages/time/src/core/weekInfoPolyfill/weekInfoData.ts new file mode 100644 index 0000000..26c8205 --- /dev/null +++ b/packages/time/src/core/weekInfoPolyfill/weekInfoData.ts @@ -0,0 +1,1832 @@ +export const weekInfoData: Record = { + af: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ak: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sq: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + am: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ar: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hy: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + as: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + asa: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + az: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bm: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + eu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + be: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bem: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bez: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bs: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + my: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ca: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + tzm: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + chr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + cgg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + zh: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + cs: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + da: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ebu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + en: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + eo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + et: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ee: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fil: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fi: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ff: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ka: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + de: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + el: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + guz: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ha: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + haw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + he: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hi: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + hu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + is: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ig: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + id: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ga: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + it: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ja: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kea: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kab: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kln: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kam: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + km: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ki: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kok: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ko: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + khq: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ses: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lag: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lv: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + lt: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + luo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + luy: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + jmc: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + kde: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ms: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ml: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mt: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gv: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mas: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mer: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + mfe: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + naq: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ne: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nd: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nb: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + nyn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + or: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + om: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ps: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + fa: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pt: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + pa: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ro: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rm: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rof: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ru: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + rwk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + saq: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sg: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + seh: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sn: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ii: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + si: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sl: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + xog: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + so: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + es: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + sv: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + gsw: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + shi: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + dav: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ta: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + te: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + teo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + th: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + bo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ti: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + to: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + tr: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + uk: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + ur: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + uz: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + vi: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + vun: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + cy: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + yo: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + zu: { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'af-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'am-ET': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-AE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-BH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-DZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-EG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-IQ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-JO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-KW': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-LB': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-LY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-MA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'arn-CL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-OM': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-QA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-SA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-SD': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-SY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-TN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ar-YE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'as-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'az-az': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'az-Cyrl-AZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'az-Latn-AZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ba-RU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'be-BY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bg-BG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bn-BD': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bn-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bo-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'br-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bs-Cyrl-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'bs-Latn-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ca-ES': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'co-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'cs-CZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'cy-GB': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'da-DK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-AT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-CH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-DE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-LI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'de-LU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'dsb-DE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'dv-MV': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'el-CY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'el-GR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-029': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-AU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-BZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-cb': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-GB': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-IE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-JM': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-MT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-MY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-NZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-PH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-SG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-TT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-US': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'en-ZW': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-AR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-BO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-CL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-CO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-CR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-DO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-EC': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-ES': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-GT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-HN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-MX': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-NI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-PA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-PE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-PR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-PY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-SV': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-US': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-UY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'es-VE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'et-EE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'eu-ES': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fa-IR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fi-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fil-PH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fo-FO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-BE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-CH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-LU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fr-MC': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'fy-NL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ga-IE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gd-GB': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gd-ie': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gl-ES': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gsw-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'gu-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ha-Latn-NG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'he-IL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hi-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hr-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hr-HR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hsb-DE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hu-HU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'hy-AM': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'id-ID': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ig-NG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ii-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'in-ID': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'is-IS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'it-CH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'it-IT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'iu-Cans-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'iu-Latn-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'iw-IL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ja-JP': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ka-GE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'kk-KZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'kl-GL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'km-KH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'kn-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'kok-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ko-KR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ky-KG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'lb-LU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'lo-LA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'lt-LT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'lv-LV': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mi-NZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mk-MK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ml-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mn-MN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mn-Mong-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'moh-CA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mr-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ms-BN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ms-MY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'mt-MT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nb-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ne-NP': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nl-BE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nl-NL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nn-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'no-no': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'nso-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'oc-FR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'or-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'pa-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'pl-PL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'prs-AF': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ps-AF': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'pt-BR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'pt-PT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'qut-GT': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'quz-BO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'quz-EC': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'quz-PE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'rm-CH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ro-mo': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ro-RO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ru-mo': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ru-RU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'rw-RW': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sah-RU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sa-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'se-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'se-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'se-SE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'si-LK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sk-SK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sl-SI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sma-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sma-SE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'smj-NO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'smj-SE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'smn-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sms-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sq-AL': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-CS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Cyrl-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Cyrl-CS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Cyrl-ME': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Cyrl-RS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Latn-BA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Latn-CS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Latn-ME': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-Latn-RS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-ME': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-RS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sr-sp': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sv-FI': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sv-SE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'sw-KE': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'syr-SY': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ta-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'te-IN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tg-Cyrl-TJ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'th-TH': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tk-TM': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tlh-QS': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tn-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tr-TR': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tt-RU': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'tzm-Latn-DZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ug-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'uk-UA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'ur-PK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'uz-Cyrl-UZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'uz-Latn-UZ': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'uz-uz': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'vi-VN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'wo-SN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'xh-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'yo-NG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-CN': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-HK': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-MO': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-SG': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zh-TW': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, + 'zu-ZA': { + firstDay: 1, + weekend: [6, 7], + minimalDays: 4, + }, +} \ No newline at end of file diff --git a/packages/time/src/core/weekInfoPolyfill/weekInfoPolyfill.ts b/packages/time/src/core/weekInfoPolyfill/weekInfoPolyfill.ts new file mode 100644 index 0000000..a722d0a --- /dev/null +++ b/packages/time/src/core/weekInfoPolyfill/weekInfoPolyfill.ts @@ -0,0 +1,41 @@ +interface WeekInfo { + firstDay: number + weekend: number[] + minimalDays: number +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Intl { + interface Locale { + getWeekInfo: () => WeekInfo + weekInfo?: WeekInfo + } +} + +;(function () { + // Chrome & Safari + if ('weekInfo' in Intl.Locale.prototype && typeof Intl.Locale.prototype.getWeekInfo !== 'function') { + Intl.Locale.prototype.getWeekInfo = function () { + return this.weekInfo + } + } + // Firefox + if (typeof Intl.Locale.prototype.getWeekInfo !== 'function') { + import('./weekInfoData').then(({ weekInfoData }) => { + Intl.Locale.prototype.getWeekInfo = function () { + const locale = this.toString().toLowerCase() + + const match = + weekInfoData[locale] || + weekInfoData[locale.split('-')[0]] || + weekInfoData['default'] + + return { + firstDay: match?.firstDay, + weekend: match?.weekend, + minimalDays: match?.minimalDays, + } + } + }) + } +})() diff --git a/packages/time/src/utils/getFirstDayOfWeek.ts b/packages/time/src/utils/getFirstDayOfWeek.ts index 65f71e3..31224f1 100644 --- a/packages/time/src/utils/getFirstDayOfWeek.ts +++ b/packages/time/src/utils/getFirstDayOfWeek.ts @@ -3,6 +3,6 @@ import { Temporal } from '@js-temporal/polyfill' export const getFirstDayOfWeek = (currWeek: string, locale: Intl.UnicodeBCP47LocaleIdentifier | Intl.Locale = 'en-US') => { const date = Temporal.PlainDate.from(currWeek); const loc = new Intl.Locale(locale); - const { firstDay } = loc.weekInfo || loc.getWeekInfo(); + const { firstDay } = loc.getWeekInfo(); return date.subtract({ days: (date.dayOfWeek - firstDay + 7) % 7 }); } From a80513107489553e5217285c50e42b806f014f10 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 30 Jun 2024 21:13:33 +0200 Subject: [PATCH 096/128] refactor: add a resources field to event --- packages/time/src/calendar/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index 4b637de..f18e4fb 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -1,10 +1,11 @@ import type { Temporal } from "@js-temporal/polyfill" -export interface Event { +export interface Event { id: string; start: Temporal.PlainDateTime | Temporal.ZonedDateTime; end: Temporal.PlainDateTime | Temporal.ZonedDateTime; title: string; + resources?: TResource[]; } export interface CalendarStore { From e17f0cd684a8f5d12157fab9a00ace05181873ba Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 30 Jun 2024 21:19:28 +0200 Subject: [PATCH 097/128] refactor: add a resources field to event --- packages/time/src/core/calendar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index eb3ca01..6cf341f 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -30,7 +30,7 @@ export interface ViewMode { * of events, locale, time zone, and the calendar system. * @template TEvent - Specifies the event type, extending a base Event type. */ -export interface CalendarCoreOptions { +export interface CalendarCoreOptions> { /** An optional array of events to be handled by the calendar. */ events?: TEvent[] | null /** The initial view mode configuration of the calendar. */ @@ -41,6 +41,8 @@ export interface CalendarCoreOptions { timeZone?: Temporal.TimeZoneLike /** Optional calendar system to be used. */ calendar?: Temporal.CalendarLike + /** Optional resources to be used in the calendar. */ + resources?: TResource[] } /** From 396d6291de0594c13e2b9473680b53ccef4473a9 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 30 Jun 2024 22:19:52 +0200 Subject: [PATCH 098/128] refactor: add a resources field to event --- packages/time/src/calendar/groupDaysBy.ts | 109 +++++++++++------- .../time/src/calendar/splitMultiDayEvents.ts | 58 +++++++--- packages/time/src/calendar/types.ts | 23 ++-- packages/time/src/core/calendar.ts | 39 ++++--- packages/time/src/tests/calendar-core.test.ts | 4 +- 5 files changed, 146 insertions(+), 87 deletions(-) diff --git a/packages/time/src/calendar/groupDaysBy.ts b/packages/time/src/calendar/groupDaysBy.ts index 8b19e98..dbd3af6 100644 --- a/packages/time/src/calendar/groupDaysBy.ts +++ b/packages/time/src/calendar/groupDaysBy.ts @@ -1,55 +1,78 @@ -import { Temporal } from "@js-temporal/polyfill"; -import type { Day, Event } from "./types"; +import { Temporal } from '@js-temporal/polyfill' +import type { Day, Event, Resource } from './types' -interface GroupDaysByBaseProps { - days: (Day | null)[]; - weekStartsOn: number; +interface GroupDaysByBaseProps< + TResource extends Resource, + TEvent extends Event = Event, +> { + days: (Day | null)[] + weekStartsOn: number } -type GroupDaysByMonthProps = GroupDaysByBaseProps & { - unit: 'month'; - fillMissingDays?: never; -}; +type GroupDaysByMonthProps< + TResource extends Resource, + TEvent extends Event = Event, +> = GroupDaysByBaseProps & { + unit: 'month' + fillMissingDays?: never +} -type GroupDaysByWeekProps = GroupDaysByBaseProps & { - unit: 'week'; - fillMissingDays?: boolean; -}; +type GroupDaysByWeekProps< + TResource extends Resource, + TEvent extends Event = Event, +> = GroupDaysByBaseProps & { + unit: 'week' + fillMissingDays?: boolean +} -export type GroupDaysByProps = GroupDaysByMonthProps | GroupDaysByWeekProps; +export type GroupDaysByProps< + TResource extends Resource, + TEvent extends Event = Event, +> = + | GroupDaysByMonthProps + | GroupDaysByWeekProps -export const groupDaysBy = ({ +export const groupDaysBy = < + TResource extends Resource, + TEvent extends Event = Event, +>({ days, unit, fillMissingDays = true, weekStartsOn, -}: GroupDaysByProps): (Day | null)[][] => { - const groups: (Day | null)[][] = []; +}: GroupDaysByProps): (Day< + TResource, + TEvent +> | null)[][] => { + const groups: (Day | null)[][] = [] switch (unit) { case 'month': { - let currentMonth: (Day | null)[] = []; + let currentMonth: (Day | null)[] = [] days.forEach((day) => { - if (currentMonth.length > 0 && day?.date.month !== currentMonth[0]?.date.month) { - groups.push(currentMonth); - currentMonth = []; + if ( + currentMonth.length > 0 && + day?.date.month !== currentMonth[0]?.date.month + ) { + groups.push(currentMonth) + currentMonth = [] } - currentMonth.push(day); - }); + currentMonth.push(day) + }) if (currentMonth.length > 0) { - groups.push(currentMonth); + groups.push(currentMonth) } - break; + break } case 'week': { - const weeks: (Day | null)[][] = []; - let currentWeek: (Day | null)[] = []; + const weeks: (Day | null)[][] = [] + let currentWeek: (Day | null)[] = [] days.forEach((day) => { if (currentWeek.length === 0 && day?.date.dayOfWeek !== weekStartsOn) { if (day) { - const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7; + const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7 for (let i = 0; i < dayOfWeek; i++) { currentWeek.push( fillMissingDays @@ -59,21 +82,23 @@ export const groupDaysBy = ({ isToday: false, isInCurrentPeriod: false, } - : null - ); + : null, + ) } } } - currentWeek.push(day); + currentWeek.push(day) if (currentWeek.length === 7) { - weeks.push(currentWeek); - currentWeek = []; + weeks.push(currentWeek) + currentWeek = [] } - }); + }) if (currentWeek.length > 0) { while (currentWeek.length < 7) { - const lastDate = currentWeek[currentWeek.length - 1]?.date ?? Temporal.PlainDate.from('2024-01-01'); + const lastDate = + currentWeek[currentWeek.length - 1]?.date ?? + Temporal.PlainDate.from('2024-01-01') currentWeek.push( fillMissingDays ? { @@ -82,16 +107,16 @@ export const groupDaysBy = ({ isToday: false, isInCurrentPeriod: false, } - : null - ); + : null, + ) } - weeks.push(currentWeek); + weeks.push(currentWeek) } - return weeks; + return weeks } default: - break; + break } - return groups; -}; + return groups +} diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts index 4fb1560..4117784 100644 --- a/packages/time/src/calendar/splitMultiDayEvents.ts +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -1,23 +1,51 @@ -import { Temporal } from '@js-temporal/polyfill'; -import type { Event } from './types'; +import { Temporal } from '@js-temporal/polyfill' +import type { Event } from './types' -export const splitMultiDayEvents = (event: TEvent, timeZone: Temporal.TimeZoneLike): TEvent[] => { - const startDate = event.start instanceof Temporal.PlainDateTime ? event.start.toZonedDateTime(timeZone) : event.start; - const endDate = event.end instanceof Temporal.PlainDateTime ? event.end.toZonedDateTime(timeZone) : event.end; - const events: TEvent[] = []; +export const splitMultiDayEvents = < + TResource extends string | null = null, + TEvent extends Event = Event, +>( + event: TEvent, + timeZone: Temporal.TimeZoneLike, +): TEvent[] => { + const startDate = + event.start instanceof Temporal.PlainDateTime + ? event.start.toZonedDateTime(timeZone) + : event.start + const endDate = + event.end instanceof Temporal.PlainDateTime + ? event.end.toZonedDateTime(timeZone) + : event.end + const events: TEvent[] = [] - let currentDay = startDate; + let currentDay = startDate while (Temporal.ZonedDateTime.compare(currentDay, endDate) < 0) { - const startOfCurrentDay = currentDay.with({ hour: 0, minute: 0, second: 0, millisecond: 0 }); - const endOfCurrentDay = currentDay.with({ hour: 23, minute: 59, second: 59, millisecond: 999 }); + const startOfCurrentDay = currentDay.with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }) + const endOfCurrentDay = currentDay.with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }) - const eventStart = Temporal.PlainDateTime.compare(currentDay, startDate) === 0 ? startDate : startOfCurrentDay; - const eventEnd = Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 ? endDate : endOfCurrentDay; + const eventStart = + Temporal.PlainDateTime.compare(currentDay, startDate) === 0 + ? startDate + : startOfCurrentDay + const eventEnd = + Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 + ? endDate + : endOfCurrentDay - events.push({ ...event, start: eventStart, end: eventEnd }); + events.push({ ...event, start: eventStart, end: eventEnd }) - currentDay = startOfCurrentDay.add({ days: 1 }); + currentDay = startOfCurrentDay.add({ days: 1 }) } - return events; -}; + return events +} diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index f18e4fb..3157227 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -1,11 +1,13 @@ -import type { Temporal } from "@js-temporal/polyfill" +import type { Temporal } from '@js-temporal/polyfill' -export interface Event { - id: string; - start: Temporal.PlainDateTime | Temporal.ZonedDateTime; - end: Temporal.PlainDateTime | Temporal.ZonedDateTime; - title: string; - resources?: TResource[]; +export type Resource = string | null + +export interface Event { + id: string + start: Temporal.PlainDateTime | Temporal.ZonedDateTime + end: Temporal.PlainDateTime | Temporal.ZonedDateTime + title: string + resources?: TResource[] } export interface CalendarStore { @@ -16,9 +18,12 @@ export interface CalendarStore { } } -export type Day = { +export type Day< + TResource extends string | null = null, + TEvent extends Event = Event, +> = { date: Temporal.PlainDate events: TEvent[] isToday: boolean isInCurrentPeriod: boolean -} \ No newline at end of file +} diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 6cf341f..38a98fd 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -8,7 +8,7 @@ import { groupDaysBy } from '../calendar/groupDaysBy' import { getDateDefaults } from '../utils/dateDefaults' import type { Properties as CSSProperties } from 'csstype' import type { GroupDaysByProps } from '../calendar/groupDaysBy' -import type { CalendarStore, Day, Event } from '../calendar/types' +import type { CalendarStore, Day, Event, Resource } from '../calendar/types' import './weekInfoPolyfill' @@ -30,7 +30,7 @@ export interface ViewMode { * of events, locale, time zone, and the calendar system. * @template TEvent - Specifies the event type, extending a base Event type. */ -export interface CalendarCoreOptions> { +export interface CalendarCoreOptions> { /** An optional array of events to be handled by the calendar. */ events?: TEvent[] | null /** The initial view mode configuration of the calendar. */ @@ -42,7 +42,7 @@ export interface CalendarCoreOptions { +interface CalendarActions> { /** Navigates to the previous period according to the current view mode. */ goToPreviousPeriod: () => void /** Navigates to the next period according to the current view mode. */ @@ -65,37 +65,37 @@ interface CalendarActions { getEventProps: (id: Event['id']) => { style: CSSProperties } | null /** Groups days by a specified unit. */ groupDaysBy: ( - props: Omit, 'weekStartsOn'>, - ) => (Day | null)[][] + props: Omit, 'weekStartsOn'>, + ) => (Day | null)[][] } -interface CalendarState { +interface CalendarState> { /** The currently focused date period in the calendar. */ currentPeriod: CalendarStore['currentPeriod'] /** The current view mode of the calendar. */ viewMode: CalendarStore['viewMode'] /** An array of days, each potentially containing events. */ - days: Array> + days: Array> /** An array of names for the days of the week, localized to the calendar's locale. */ daysNames: string[] } -export interface CalendarApi - extends CalendarActions, - CalendarState {} +export interface CalendarApi> + extends CalendarActions, + CalendarState {} /** * Core functionality for a calendar system, managing the state and operations of the calendar, * such as navigating through time periods, handling events, and adjusting settings. * @template TEvent - The type of events managed by the calendar. */ -export class CalendarCore - implements CalendarActions +export class CalendarCore> + implements CalendarActions { store: Store - options: Required> + options: Required> - constructor(options: CalendarCoreOptions) { + constructor(options: CalendarCoreOptions) { const defaults = getDateDefaults() this.options = { ...options, @@ -103,6 +103,7 @@ export class CalendarCore timeZone: options.timeZone || defaults.timeZone, calendar: options.calendar || defaults.calendar, events: options.events || null, + resources: options.resources || null, } this.store = new Store({ @@ -191,7 +192,7 @@ export class CalendarCore ? event.end.toZonedDateTime(this.options.timeZone) : event.end if (Temporal.ZonedDateTime.compare(eventStartDate, eventEndDate) !== 0) { - const splitEvents = splitMultiDayEvents( + const splitEvents = splitMultiDayEvents( event, this.options.timeZone, ) @@ -352,12 +353,12 @@ export class CalendarCore days, unit, fillMissingDays = true, - }: Omit, 'weekStartsOn'>) { - return groupDaysBy({ + }: Omit, 'weekStartsOn'>) { + return groupDaysBy({ days, unit, fillMissingDays, weekStartsOn: this.getFirstDayOfWeek().dayOfWeek, - } as GroupDaysByProps) + } as GroupDaysByProps) } } diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index c12f74b..9190670 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -4,8 +4,8 @@ import { CalendarCore } from '../core/calendar'; import type { CalendarCoreOptions, Event } from '../core/calendar'; describe('CalendarCore', () => { - let options: CalendarCoreOptions; - let calendarCore: CalendarCore; + let options: CalendarCoreOptions>; + let calendarCore: CalendarCore>; const mockDate = Temporal.PlainDate.from('2023-06-15'); const mockDateTime = Temporal.PlainDateTime.from('2023-06-15T10:00'); const mockTimeZone = 'America/New_York'; From adc999877927832c258b0311666369ad0f008069 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 30 Jun 2024 22:43:55 +0200 Subject: [PATCH 099/128] refactor: add startOf and endOf helpers --- .../time/src/calendar/splitMultiDayEvents.ts | 22 +++++-------------- packages/time/src/utils/endOf.ts | 15 +++++++++++++ packages/time/src/utils/index.ts | 8 ++++--- packages/time/src/utils/startOf.ts | 17 ++++++++++++++ 4 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 packages/time/src/utils/endOf.ts create mode 100644 packages/time/src/utils/startOf.ts diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts index 4117784..41a8430 100644 --- a/packages/time/src/calendar/splitMultiDayEvents.ts +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -1,4 +1,5 @@ import { Temporal } from '@js-temporal/polyfill' +import { endOf, startOf } from '../utils' import type { Event } from './types' export const splitMultiDayEvents = < @@ -20,31 +21,18 @@ export const splitMultiDayEvents = < let currentDay = startDate while (Temporal.ZonedDateTime.compare(currentDay, endDate) < 0) { - const startOfCurrentDay = currentDay.with({ - hour: 0, - minute: 0, - second: 0, - millisecond: 0, - }) - const endOfCurrentDay = currentDay.with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }) - const eventStart = Temporal.PlainDateTime.compare(currentDay, startDate) === 0 ? startDate - : startOfCurrentDay + : startOf(currentDay) const eventEnd = - Temporal.PlainDateTime.compare(endDate, endOfCurrentDay) <= 0 + Temporal.PlainDateTime.compare(endDate, endOf(currentDay)) <= 0 ? endDate - : endOfCurrentDay + : endOf(currentDay) events.push({ ...event, start: eventStart, end: eventEnd }) - currentDay = startOfCurrentDay.add({ days: 1 }) + currentDay = startOf(currentDay).add({ days: 1 }) } return events diff --git a/packages/time/src/utils/endOf.ts b/packages/time/src/utils/endOf.ts new file mode 100644 index 0000000..4742df9 --- /dev/null +++ b/packages/time/src/utils/endOf.ts @@ -0,0 +1,15 @@ +import type { Temporal } from '@js-temporal/polyfill' + +/** + * Helper function to get the end of a given day. + * @param date - The date for which the end of the day is needed. + * @returns The end of the given day. + */ +export const endOf = (date: Temporal.ZonedDateTime): Temporal.ZonedDateTime => { + return date.with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }) +} diff --git a/packages/time/src/utils/index.ts b/packages/time/src/utils/index.ts index 293009a..a744ffa 100644 --- a/packages/time/src/utils/index.ts +++ b/packages/time/src/utils/index.ts @@ -1,3 +1,5 @@ -export * from './parse'; -export * from './getFirstDayOfMonth'; -export * from './getFirstDayOfWeek'; +export * from './parse' +export * from './getFirstDayOfMonth' +export * from './getFirstDayOfWeek' +export * from './startOf' +export * from './endOf' diff --git a/packages/time/src/utils/startOf.ts b/packages/time/src/utils/startOf.ts new file mode 100644 index 0000000..d5723e3 --- /dev/null +++ b/packages/time/src/utils/startOf.ts @@ -0,0 +1,17 @@ +import type { Temporal } from '@js-temporal/polyfill' + +/** + * Helper function to get the start of a given day. + * @param date - The date for which the start of the day is needed. + * @returns The start of the given day. + */ +export const startOf = ( + date: Temporal.ZonedDateTime, +): Temporal.ZonedDateTime => { + return date.with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }) +} From 31470b431ee445f3aaf6d97fc7a80f73857690ab Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 30 Jun 2024 23:58:38 +0200 Subject: [PATCH 100/128] refactor: remove all the layout specific code --- .../react-time/src/tests/useCalendar.test.tsx | 33 ++++---------- .../react-time/src/useCalendar/useCalendar.ts | 10 ++--- packages/time/src/calendar/getEventProps.ts | 29 ++----------- .../time/src/calendar/splitMultiDayEvents.ts | 4 +- packages/time/src/calendar/types.ts | 2 +- packages/time/src/core/calendar.ts | 43 ++++++++++++++----- 6 files changed, 53 insertions(+), 68 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 2159dd8..64f8d6d 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -93,14 +93,9 @@ describe('useCalendar', () => { const eventProps = result.current.getEventProps('1') expect(eventProps).toEqual({ - style: { - position: 'absolute', - top: 'min(41.66666666666667%, calc(100% - 55px))', - left: '2%', - width: '96%', - margin: 0, - height: '8.333333333333332%', - }, + eventHeightInMinutes: 120, + isSplitEvent: false, + overlappingEvents: [], }) }) @@ -127,25 +122,15 @@ describe('useCalendar', () => { const event2Props = result.current.getEventProps('2') expect(event1Props).toEqual({ - style: { - position: 'absolute', - top: 'min(41.66666666666667%, calc(100% - 55px))', - left: '2%', - width: '47%', - margin: 0, - height: '8.333333333333332%', - }, + eventHeightInMinutes: 120, + isSplitEvent: false, + overlappingEvents: overlappingEvents[1] }) expect(event2Props).toEqual({ - style: { - position: 'absolute', - top: 'min(45.83333333333333%, calc(100% - 55px))', - left: '51%', - width: '47%', - margin: 0, - height: '8.333333333333332%', - }, + eventHeightInMinutes: 120, + isSplitEvent: false, + overlappingEvents: overlappingEvents[0] }) }) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 8dd5ea7..52b7322 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -1,12 +1,12 @@ import { useCallback, useState, useTransition } from 'react' import { useStore } from '@tanstack/react-store' import { CalendarCore, type Event } from '@tanstack/time' -import type { CalendarApi, CalendarCoreOptions } from '@tanstack/time' +import type { CalendarApi, CalendarCoreOptions, Resource } from '@tanstack/time' -export const useCalendar = ( - options: CalendarCoreOptions, -): CalendarApi & { isPending: boolean } => { - const [calendarCore] = useState(() => new CalendarCore(options)) +export const useCalendar = = Event>( + options: CalendarCoreOptions, +): CalendarApi & { isPending: boolean } => { + const [calendarCore] = useState(() => new CalendarCore(options)) const state = useStore(calendarCore.store) const [isPending, startTransition] = useTransition() diff --git a/packages/time/src/calendar/getEventProps.ts b/packages/time/src/calendar/getEventProps.ts index cd6ce5e..7e3c0d1 100644 --- a/packages/time/src/calendar/getEventProps.ts +++ b/packages/time/src/calendar/getEventProps.ts @@ -1,12 +1,11 @@ import { Temporal } from "@js-temporal/polyfill"; -import type { Properties as CSSProperties } from "csstype"; import type { CalendarStore, Event } from "./types"; export const getEventProps = ( eventMap: Map, id: Event['id'], state: CalendarStore, -): { style: CSSProperties } | null => { +) => { const event = [...eventMap.values()].flat().find((currEvent) => currEvent.id === id); if (!event) return null; @@ -14,28 +13,22 @@ export const getEventProps = ( const eventEndDate = Temporal.ZonedDateTime.from(event.end); const isSplitEvent = Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0; - let percentageOfDay; let eventHeightInMinutes; if (isSplitEvent) { const isStartPart = eventStartDate.hour !== 0 || eventStartDate.minute !== 0; if (isStartPart) { const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute; - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100; eventHeightInMinutes = 24 * 60 - eventTimeInMinutes; } else { - percentageOfDay = 0; eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; } } else { const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute; - percentageOfDay = (eventTimeInMinutes / (24 * 60)) * 100; const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes; } - const eventHeight = Math.min((eventHeightInMinutes / (24 * 60)) * 100, 20); - const overlappingEvents = [...eventMap.values()].flat().filter((e) => { const eStartDate = Temporal.ZonedDateTime.from(e.start); const eEndDate = Temporal.ZonedDateTime.from(e.end); @@ -52,25 +45,11 @@ export const getEventProps = ( ); }); - const eventIndex = overlappingEvents.findIndex((e) => e.id === id); - const totalOverlaps = overlappingEvents.length; - const sidePadding = 2; - const innerPadding = 2; - const totalInnerPadding = (totalOverlaps - 1) * innerPadding; - const availableWidth = 100 - totalInnerPadding - 2 * sidePadding; - const eventWidth = totalOverlaps > 0 ? availableWidth / totalOverlaps : 100 - 2 * sidePadding; - const eventLeft = sidePadding + eventIndex * (eventWidth + innerPadding); - if (state.viewMode.unit === 'week' || state.viewMode.unit === 'day') { return { - style: { - position: 'absolute', - top: `min(${percentageOfDay}%, calc(100% - 55px))`, - left: `${eventLeft}%`, - width: `${eventWidth}%`, - margin: 0, - height: `${eventHeight}%`, - }, + eventHeightInMinutes, + isSplitEvent, + overlappingEvents, }; } diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts index 41a8430..8aa58e6 100644 --- a/packages/time/src/calendar/splitMultiDayEvents.ts +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -1,9 +1,9 @@ import { Temporal } from '@js-temporal/polyfill' import { endOf, startOf } from '../utils' -import type { Event } from './types' +import type { Event, Resource } from './types' export const splitMultiDayEvents = < - TResource extends string | null = null, + TResource extends Resource = Resource, TEvent extends Event = Event, >( event: TEvent, diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index 3157227..05933b9 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -19,7 +19,7 @@ export interface CalendarStore { } export type Day< - TResource extends string | null = null, + TResource extends Resource = Resource, TEvent extends Event = Event, > = { date: Temporal.PlainDate diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 38a98fd..a9c7554 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -6,13 +6,12 @@ import { splitMultiDayEvents } from '../calendar/splitMultiDayEvents' import { getEventProps } from '../calendar/getEventProps' import { groupDaysBy } from '../calendar/groupDaysBy' import { getDateDefaults } from '../utils/dateDefaults' -import type { Properties as CSSProperties } from 'csstype' import type { GroupDaysByProps } from '../calendar/groupDaysBy' import type { CalendarStore, Day, Event, Resource } from '../calendar/types' import './weekInfoPolyfill' -export type { CalendarStore, Event, Day } from '../calendar/types' +export type * from '../calendar/types' /** * Represents the configuration for the current viewing mode of a calendar, @@ -30,7 +29,10 @@ export interface ViewMode { * of events, locale, time zone, and the calendar system. * @template TEvent - Specifies the event type, extending a base Event type. */ -export interface CalendarCoreOptions> { +export interface CalendarCoreOptions< + TResource extends Resource, + TEvent extends Event, +> { /** An optional array of events to be handled by the calendar. */ events?: TEvent[] | null /** The initial view mode configuration of the calendar. */ @@ -50,7 +52,10 @@ export interface CalendarCoreOptions> { +interface CalendarActions< + TResource extends Resource, + TEvent extends Event, +> { /** Navigates to the previous period according to the current view mode. */ goToPreviousPeriod: () => void /** Navigates to the next period according to the current view mode. */ @@ -62,14 +67,22 @@ interface CalendarActions void /** Retrieves styling properties for a specific event, identified by ID. */ - getEventProps: (id: Event['id']) => { style: CSSProperties } | null + getEventProps: (id: Event['id']) => { + eventHeightInMinutes: number + isSplitEvent: boolean + overlappingEvents: TEvent[] + } | null + /** Groups days by a specified unit. */ groupDaysBy: ( props: Omit, 'weekStartsOn'>, ) => (Day | null)[][] } -interface CalendarState> { +interface CalendarState< + TResource extends Resource, + TEvent extends Event, +> { /** The currently focused date period in the calendar. */ currentPeriod: CalendarStore['currentPeriod'] /** The current view mode of the calendar. */ @@ -80,8 +93,10 @@ interface CalendarState> - extends CalendarActions, +export interface CalendarApi< + TResource extends Resource, + TEvent extends Event, +> extends CalendarActions, CalendarState {} /** @@ -89,8 +104,10 @@ export interface CalendarApi> - implements CalendarActions +export class CalendarCore< + TResource extends Resource, + TEvent extends Event, +> implements CalendarActions { store: Store options: Required> @@ -346,7 +363,11 @@ export class CalendarCore['getEventProps']> } groupDaysBy({ From 16867e8d8f4ca91b3716b85cc0538ae394cb0a2f Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 1 Jul 2024 22:51:15 +0200 Subject: [PATCH 101/128] refactor: update getDaysNames method to accept weekday parameter --- .../react-time/src/tests/useCalendar.test.tsx | 7 ++++--- .../react-time/src/useCalendar/useCalendar.ts | 4 +++- packages/time/src/core/calendar.ts | 16 +++++++++------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 64f8d6d..15f7419 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -25,7 +25,7 @@ describe('useCalendar', () => { ) expect(result.current.viewMode).toEqual({ value: 1, unit: 'month' }) expect(result.current.currentPeriod.toString()).toBe( - Temporal.Now.plainDateISO().toString(), + Temporal.Now.plainDateISO().withCalendar('gregory').toString(), ) }) @@ -155,8 +155,9 @@ describe('useCalendar', () => { useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) ); - const { daysNames } = result.current; - expect(daysNames).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']); + const { getDaysNames } = result.current; + const daysNames = getDaysNames(); + expect(daysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']); }); test('should correctly mark days as in current period', () => { diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 52b7322..8286036 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -44,10 +44,12 @@ export const useCalendar = ((props) => calendarCore.groupDaysBy(props), [calendarCore]) + const getDaysNames = useCallback((props) => calendarCore.getDaysNames(props), [calendarCore]) + return { ...state, days: calendarCore.getDaysWithEvents(), - daysNames: calendarCore.getDaysNames(), + getDaysNames, goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index a9c7554..b969959 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -72,6 +72,8 @@ interface CalendarActions< isSplitEvent: boolean overlappingEvents: TEvent[] } | null + /** Retrieves the names of the days of the week, based on the current locale. */ + getDaysNames: (weekday?: 'long' | 'short') => string[] /** Groups days by a specified unit. */ groupDaysBy: ( @@ -89,8 +91,6 @@ interface CalendarState< viewMode: CalendarStore['viewMode'] /** An array of days, each potentially containing events. */ days: Array> - /** An array of names for the days of the week, localized to the calendar's locale. */ - daysNames: string[] } export interface CalendarApi< @@ -252,13 +252,15 @@ export class CalendarCore< }) } - getDaysNames() { - const baseDate = Temporal.PlainDate.from('2024-01-01') + getDaysNames(weekday: 'long' | 'short' = 'short') { + const baseDate = Temporal.PlainDate.from('2024-01-01'); + const firstDayOfWeek = this.getFirstDayOfWeek().dayOfWeek; + return Array.from({ length: 7 }).map((_, i) => baseDate - .add({ days: (i + (this.getFirstDayOfWeek().dayOfWeek + 1)) % 7 }) - .toLocaleString(this.options.locale, { weekday: 'short' }), - ) + .add({ days: (i + (firstDayOfWeek - 1)) % 7 }) + .toLocaleString(this.options.locale, { weekday: weekday }), + ); } changeViewMode(newViewMode: CalendarStore['viewMode']) { From be687989b13e6e09e8e945d7b1d83e8bd5496b14 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 1 Jul 2024 23:07:42 +0200 Subject: [PATCH 102/128] refactor: update useCalendar test to mock current date and time --- .../react-time/src/tests/useCalendar.test.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 15f7419..79b511d 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -1,5 +1,5 @@ import { Temporal } from '@js-temporal/polyfill' -import { describe, expect, test } from 'vitest' +import { beforeEach, describe, expect, test, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' import { useCalendar } from '../useCalendar' @@ -19,6 +19,18 @@ describe('useCalendar', () => { }, ] + + const mockDate = Temporal.PlainDate.from('2024-06-15'); + const mockDateTime = Temporal.PlainDateTime.from('2024-06-15T10:00'); + const mockTimeZone = 'America/New_York'; + + beforeEach(() => { + vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate); + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(mockDateTime); + vi.spyOn(Temporal.Now, 'zonedDateTime').mockReturnValue(Temporal.Now.zonedDateTime('gregory',mockTimeZone)); + vi.spyOn(Temporal.Now, 'zonedDateTimeISO').mockReturnValue(Temporal.Now.zonedDateTimeISO()); + }); + test('should initialize with the correct view mode and current period', () => { const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), @@ -142,12 +154,12 @@ describe('useCalendar', () => { const { days } = result.current; const weeks = result.current.groupDaysBy({ days, unit: 'week' }); - expect(weeks).toHaveLength(5); + expect(weeks).toHaveLength(6); expect(weeks[0]).toHaveLength(7); - expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-27'); - expect(weeks[weeks.length - 1]?.[0]?.date.toString()).toBe('2024-06-24'); - expect(weeks.find((week) => week.some((day) => day?.isToday))?.find((day) => day?.isToday)?.date.toString()).toBe('2024-06-01'); + expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-26'); + expect(weeks[weeks.length - 1]?.[0]?.date.toString()).toBe('2024-06-30'); + expect(weeks.find((week) => week.some((day) => day?.isToday))?.find((day) => day?.isToday)?.date.toString()).toBe('2024-06-15'); }); test('should return the correct day names based on the locale', () => { From c3ba0c9d1e5fc02917d6bce96316519bc1a08389 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 1 Jul 2024 23:07:53 +0200 Subject: [PATCH 103/128] refactor: update useCalendar test to mock current date and time --- packages/time/src/tests/calendar-core.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 9190670..63bfcbb 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -32,6 +32,8 @@ describe('CalendarCore', () => { calendarCore = new CalendarCore(options); vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate); vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(mockDateTime); + vi.spyOn(Temporal.Now, 'zonedDateTime').mockReturnValue(Temporal.Now.zonedDateTime('gregory',mockTimeZone)); + vi.spyOn(Temporal.Now, 'zonedDateTimeISO').mockReturnValue(Temporal.Now.zonedDateTimeISO()); }); test('should initialize with the correct current period', () => { From 5afea3ed8a696f94d143c7f40301713a342c1a55 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 1 Jul 2024 23:15:01 +0200 Subject: [PATCH 104/128] test: update useCalendar tests --- .../react-time/src/tests/useCalendar.test.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 79b511d..5868576 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -27,7 +27,7 @@ describe('useCalendar', () => { beforeEach(() => { vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate); vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(mockDateTime); - vi.spyOn(Temporal.Now, 'zonedDateTime').mockReturnValue(Temporal.Now.zonedDateTime('gregory',mockTimeZone)); + vi.spyOn(Temporal.Now, 'zonedDateTime').mockReturnValue(Temporal.Now.zonedDateTime('gregory', mockTimeZone)); vi.spyOn(Temporal.Now, 'zonedDateTimeISO').mockReturnValue(Temporal.Now.zonedDateTimeISO()); }); @@ -170,6 +170,14 @@ describe('useCalendar', () => { const { getDaysNames } = result.current; const daysNames = getDaysNames(); expect(daysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']); + + const { result: resultPl } = renderHook(() => + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'pl' }) + ); + + const { getDaysNames: getDaysNamesPl } = resultPl.current; + const daysNamesPl = getDaysNamesPl(); + expect(daysNamesPl).toEqual(['pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.', 'niedz.']); }); test('should correctly mark days as in current period', () => { @@ -180,13 +188,13 @@ describe('useCalendar', () => { const { days } = result.current; const weeks = result.current.groupDaysBy({ days, unit: 'week' }); const daysInCurrentPeriod = weeks.flat().map(day => day?.isInCurrentPeriod); - expect(daysInCurrentPeriod).toEqual([ - false, false, false, false, false, true, true, + false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true + true, true, true, true, true, true, true, + true, false, false, false, false, false, false ]); }); @@ -266,8 +274,8 @@ describe('useCalendar', () => { const { days, groupDaysBy } = result.current; const weeks = groupDaysBy({ days, unit: 'week' }); - expect(weeks).toHaveLength(5); - expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-27'); - expect(weeks[4]?.[6]?.date.toString()).toBe('2024-06-30'); + expect(weeks).toHaveLength(6); + expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-26'); + expect(weeks[4]?.[6]?.date.toString()).toBe('2024-06-29'); }); }); From 15aced55e529b7f6d627531864195ccebe677410 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 1 Jul 2024 23:15:45 +0200 Subject: [PATCH 105/128] test: update useCalendar tests --- packages/react-time/src/tests/useCalendar.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index 5868576..c10cc8c 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -19,7 +19,6 @@ describe('useCalendar', () => { }, ] - const mockDate = Temporal.PlainDate.from('2024-06-15'); const mockDateTime = Temporal.PlainDateTime.from('2024-06-15T10:00'); const mockTimeZone = 'America/New_York'; From eb8b7b7dbc97ab1a04a40ddc1d1b7dd1d7c557fb Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 2 Jul 2024 00:19:58 +0200 Subject: [PATCH 106/128] test: useCalendar --- .../react-time/src/tests/useCalendar.test.tsx | 316 ++++++------------ packages/time/src/core/calendar.ts | 4 +- packages/time/src/tests/calendar-core.test.ts | 69 +++- 3 files changed, 180 insertions(+), 209 deletions(-) diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index c10cc8c..d34af12 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -1,10 +1,26 @@ -import { Temporal } from '@js-temporal/polyfill' -import { beforeEach, describe, expect, test, vi } from 'vitest' -import { act, renderHook } from '@testing-library/react' -import { useCalendar } from '../useCalendar' +import { Temporal } from '@js-temporal/polyfill'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { act, renderHook } from '@testing-library/react'; +import { CalendarCore } from '@tanstack/time'; +import { useStore } from '@tanstack/react-store'; +import { useCalendar } from '../useCalendar'; +import type { Mock } from 'vitest'; +import type { Event } from '@tanstack/time'; + +vi.mock('@tanstack/time', () => { + return { + CalendarCore: vi.fn(), + }; +}); + +vi.mock('@tanstack/react-store', () => { + return { + useStore: vi.fn(), + }; +}); describe('useCalendar', () => { - const events = [ + const events: Event[] = [ { id: '1', start: Temporal.PlainDateTime.from('2024-06-01T10:00:00'), @@ -17,264 +33,154 @@ describe('useCalendar', () => { end: Temporal.PlainDateTime.from('2024-06-02T16:00:00'), title: 'Event 2', }, - ] + ]; const mockDate = Temporal.PlainDate.from('2024-06-15'); const mockDateTime = Temporal.PlainDateTime.from('2024-06-15T10:00'); const mockTimeZone = 'America/New_York'; + let mockStore: any; + let calendarCoreInstance: any; + beforeEach(() => { vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate); vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(mockDateTime); vi.spyOn(Temporal.Now, 'zonedDateTime').mockReturnValue(Temporal.Now.zonedDateTime('gregory', mockTimeZone)); vi.spyOn(Temporal.Now, 'zonedDateTimeISO').mockReturnValue(Temporal.Now.zonedDateTimeISO()); - }); - - test('should initialize with the correct view mode and current period', () => { - const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), - ) - expect(result.current.viewMode).toEqual({ value: 1, unit: 'month' }) - expect(result.current.currentPeriod.toString()).toBe( - Temporal.Now.plainDateISO().withCalendar('gregory').toString(), - ) - }) - - test('should navigate to the previous period correctly', () => { - const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), - ) - act(() => { - result.current.goToPreviousPeriod() - }) + mockStore = { + subscribe: vi.fn(), + state: { + currentPeriod: mockDate, + viewMode: { value: 1, unit: 'month' }, + }, + }; + + calendarCoreInstance = { + getDaysWithEvents: vi.fn().mockReturnValue([]), + getDaysNames: vi.fn().mockReturnValue([]), + goToPreviousPeriod: vi.fn(), + goToNextPeriod: vi.fn(), + goToCurrentPeriod: vi.fn(), + goToSpecificPeriod: vi.fn(), + changeViewMode: vi.fn(), + getEventProps: vi.fn().mockReturnValue({}), + groupDaysBy: vi.fn().mockReturnValue([]), + store: mockStore, + }; + + (CalendarCore as Mock).mockImplementation(() => calendarCoreInstance); + + (useStore as Mock).mockImplementation((store) => store.state); + }); - const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ - months: 1, - }) + test('should initialize CalendarCore with provided options', () => { + renderHook(() => + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, timeZone: mockTimeZone }), + ); - expect(result.current.currentPeriod).toEqual( - expectedPreviousMonth, - ) - }) + expect(CalendarCore).toHaveBeenCalledWith({ + events, + viewMode: { value: 1, unit: 'month' }, + timeZone: mockTimeZone, + }); + }); - test('should navigate to the next period correctly', () => { + test('should call goToPreviousPeriod on CalendarCore instance', () => { const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), - ) + ); act(() => { - result.current.goToNextPeriod() - }) + result.current.goToPreviousPeriod(); + }); - const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) - - expect(result.current.currentPeriod).toEqual(expectedNextMonth) - }) + expect(calendarCoreInstance.goToPreviousPeriod).toHaveBeenCalled(); + }); - test('should change view mode correctly', () => { + test('should call goToNextPeriod on CalendarCore instance', () => { const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), - ) + ); act(() => { - result.current.changeViewMode({ value: 1, unit: 'week' }) - }) + result.current.goToNextPeriod(); + }); - expect(result.current.viewMode).toEqual({ value: 1, unit: 'week' }) - }) + expect(calendarCoreInstance.goToNextPeriod).toHaveBeenCalled(); + }); - test('should select a day correctly', () => { + test('should call changeViewMode on CalendarCore instance', () => { const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), - ) - - act(() => { - result.current.goToSpecificPeriod(Temporal.PlainDate.from('2024-06-01')) - }) - - expect(result.current.currentPeriod.toString()).toBe('2024-06-01') - }) - - test('should return the correct props for an event', () => { - const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'week' } }), - ) - - const eventProps = result.current.getEventProps('1') - - expect(eventProps).toEqual({ - eventHeightInMinutes: 120, - isSplitEvent: false, - overlappingEvents: [], - }) - }) - - test('should return the correct props for overlapping events', () => { - const overlappingEvents = [ - { - id: '1', - start: Temporal.PlainDateTime.from('2024-06-01T10:00:00'), - end: Temporal.PlainDateTime.from('2024-06-01T12:00:00'), - title: 'Event 1', - }, - { - id: '2', - start: Temporal.PlainDateTime.from('2024-06-01T11:00:00'), - end: Temporal.PlainDateTime.from('2024-06-01T13:00:00'), - title: 'Event 2', - }, - ] - const { result } = renderHook(() => - useCalendar({ events: overlappingEvents, viewMode: { value: 1, unit: 'week' } }), - ) - - const event1Props = result.current.getEventProps('1') - const event2Props = result.current.getEventProps('2') - - expect(event1Props).toEqual({ - eventHeightInMinutes: 120, - isSplitEvent: false, - overlappingEvents: overlappingEvents[1] - }) - - expect(event2Props).toEqual({ - eventHeightInMinutes: 120, - isSplitEvent: false, - overlappingEvents: overlappingEvents[0] - }) - }) - - test('should render array of days', () => { - const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }), ); - const { days } = result.current; - const weeks = result.current.groupDaysBy({ days, unit: 'week' }); - - expect(weeks).toHaveLength(6); - expect(weeks[0]).toHaveLength(7); + act(() => { + result.current.changeViewMode({ value: 1, unit: 'week' }); + }); - expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-26'); - expect(weeks[weeks.length - 1]?.[0]?.date.toString()).toBe('2024-06-30'); - expect(weeks.find((week) => week.some((day) => day?.isToday))?.find((day) => day?.isToday)?.date.toString()).toBe('2024-06-15'); + expect(calendarCoreInstance.changeViewMode).toHaveBeenCalledWith({ value: 1, unit: 'week' }); }); - test('should return the correct day names based on the locale', () => { + test('should call goToSpecificPeriod on CalendarCore instance', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), ); - const { getDaysNames } = result.current; - const daysNames = getDaysNames(); - expect(daysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']); - - const { result: resultPl } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'pl' }) - ); + const specificDate = Temporal.PlainDate.from('2024-06-01'); + act(() => { + result.current.goToSpecificPeriod(specificDate); + }); - const { getDaysNames: getDaysNamesPl } = resultPl.current; - const daysNamesPl = getDaysNamesPl(); - expect(daysNamesPl).toEqual(['pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.', 'niedz.']); + expect(calendarCoreInstance.goToSpecificPeriod).toHaveBeenCalledWith(specificDate); }); - test('should correctly mark days as in current period', () => { + test('should call goToCurrentPeriod on CalendarCore instance', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), ); - const { days } = result.current; - const weeks = result.current.groupDaysBy({ days, unit: 'week' }); - const daysInCurrentPeriod = weeks.flat().map(day => day?.isInCurrentPeriod); - expect(daysInCurrentPeriod).toEqual([ - false, false, false, false, false, false, true, - true, true, true, true, true, true, true, - true, true, true, true, true, true, true, - true, true, true, true, true, true, true, - true, true, true, true, true, true, true, - true, false, false, false, false, false, false - ]); - }); - - test('should navigate to a specific period correctly', () => { - const { result } = renderHook(() => useCalendar({ events, viewMode: { value: 1, unit: 'month' } })) - const specificDate = Temporal.PlainDate.from('2024-05-15') - - act(() => { - result.current.goToSpecificPeriod(specificDate) - }) - - expect(result.current.currentPeriod).toEqual(specificDate) - }) - - test('should navigate to the previous period correctly', () => { - const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) - ) - act(() => { - result.current.goToPreviousPeriod() - }) - - const expectedPreviousMonth = Temporal.Now.plainDateISO().subtract({ - months: 1, - }) + result.current.goToNextPeriod(); + result.current.goToCurrentPeriod(); + }); - expect(result.current.currentPeriod).toEqual(expectedPreviousMonth) - }) - - test('should navigate to the next period correctly', () => { - const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) - ) - - act(() => { - result.current.goToNextPeriod() - }) - - const expectedNextMonth = Temporal.Now.plainDateISO().add({ months: 1 }) - - expect(result.current.currentPeriod).toEqual(expectedNextMonth) - }) + expect(calendarCoreInstance.goToCurrentPeriod).toHaveBeenCalled(); + }); - test('should reset to the current period correctly', () => { + test('should call getEventProps on CalendarCore instance', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) - ) + useCalendar({ events, viewMode: { value: 1, unit: 'week' } }), + ); act(() => { - result.current.goToNextPeriod() - result.current.goToCurrentPeriod() - }) + result.current.getEventProps('1'); + }); - expect(result.current.currentPeriod).toEqual( - Temporal.Now.plainDateISO(), - ) - }) + expect(calendarCoreInstance.getEventProps).toHaveBeenCalledWith('1'); + }); - test('should group days by months correctly', () => { + test('should call getDaysNames on CalendarCore instance', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 2, unit: 'month' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }), ); - const { days, groupDaysBy } = result.current; - const months = groupDaysBy({ days, unit: 'month' }); + act(() => { + result.current.getDaysNames('short'); + }); - expect(months).toHaveLength(2); - expect(months[0]?.[0]?.date.toString()).toBe('2024-06-01'); - expect(months[1]?.[0]?.date.toString()).toBe('2024-07-01'); + expect(calendarCoreInstance.getDaysNames).toHaveBeenCalledWith('short'); }); - test('should group days by weeks correctly', () => { + test('should call groupDaysBy on CalendarCore instance', () => { const { result } = renderHook(() => - useCalendar({ events, viewMode: { value: 1, unit: 'month' }, locale: 'en-US' }) + useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), ); - const { days, groupDaysBy } = result.current; - const weeks = groupDaysBy({ days, unit: 'week' }); - expect(weeks).toHaveLength(6); - expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-26'); - expect(weeks[4]?.[6]?.date.toString()).toBe('2024-06-29'); + act(() => { + result.current.groupDaysBy({ days: [], unit: 'month' }); + }); + + expect(calendarCoreInstance.groupDaysBy).toHaveBeenCalledWith({ days: [], unit: 'month' }); }); }); diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index b969959..42f23dd 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -115,10 +115,8 @@ export class CalendarCore< constructor(options: CalendarCoreOptions) { const defaults = getDateDefaults() this.options = { + ...defaults, ...options, - locale: options.locale || defaults.locale, - timeZone: options.timeZone || defaults.timeZone, - calendar: options.calendar || defaults.calendar, events: options.events || null, resources: options.resources || null, } diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 63bfcbb..a1b0929 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -26,6 +26,12 @@ describe('CalendarCore', () => { end: Temporal.PlainDateTime.from('2023-06-12T12:00'), title: 'Event 2', }, + { + id: '3', + start: Temporal.PlainDateTime.from('2023-06-12T11:00'), + end: Temporal.PlainDateTime.from('2023-06-12T13:00'), + title: 'Event 3', + }, ], timeZone: mockTimeZone, }; @@ -52,8 +58,9 @@ describe('CalendarCore', () => { const dayWithEvent2 = daysWithEvents.find((day) => day.date.equals(Temporal.PlainDate.from('2023-06-12'))); expect(dayWithEvent1?.events).toHaveLength(1); expect(dayWithEvent1?.events[0]?.id).toBe('1'); - expect(dayWithEvent2?.events).toHaveLength(1); + expect(dayWithEvent2?.events).toHaveLength(2); expect(dayWithEvent2?.events[0]?.id).toBe('2'); + expect(dayWithEvent2?.events[1]?.id).toBe('3'); }); test('should change view mode correctly', () => { @@ -108,4 +115,64 @@ describe('CalendarCore', () => { expect(calendarCore.store.state.currentPeriod.calendarId).toBe(customCalendar); expect(calendarCore.store.state.currentPeriod).toEqual(today); }); + + test('should return the correct props for an event', () => { + const eventProps = calendarCore.getEventProps('1'); + expect(eventProps).toEqual({ + eventHeightInMinutes: 60, + isSplitEvent: false, + overlappingEvents: [], + }); + }); + + test('should return the correct props for overlapping events', () => { + const event1Props = calendarCore.getEventProps('2'); + const event2Props = calendarCore.getEventProps('3'); + + expect(event1Props).toEqual({ + eventHeightInMinutes: 60, + isSplitEvent: false, + overlappingEvents: [options.events![2]], + }); + + expect(event2Props).toEqual({ + eventHeightInMinutes: 120, + isSplitEvent: false, + overlappingEvents: [options.events![1]], + }); + }); + + test('should correctly mark days as in current period', () => { + const daysWithEvents = calendarCore.getDaysWithEvents(); + const currentPeriodDays = daysWithEvents.filter(day => day.isInCurrentPeriod); + expect(currentPeriodDays).toHaveLength(30); // Assuming a 30-day month + }); + + test('should return the correct day names based on the locale', () => { + const daysNames = calendarCore.getDaysNames('short'); + expect(daysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']); + }); + + test('should reset to the current period correctly', () => { + calendarCore.goToNextPeriod(); + calendarCore.goToCurrentPeriod(); + const today = Temporal.Now.plainDateISO(); + expect(calendarCore.store.state.currentPeriod).toEqual(today); + }); + + test('should group days by weeks correctly', () => { + const daysWithEvents = calendarCore.getDaysWithEvents(); + const weeks = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'week' }); + expect(weeks).toHaveLength(6); + expect(weeks[0]?.[0]?.date.toString()).toBe('2023-05-28'); + expect(weeks[4]?.[6]?.date.toString()).toBe('2023-06-30'); + }); + + test('should group days by months correctly', () => { + const daysWithEvents = calendarCore.getDaysWithEvents(); + const months = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'month' }); + expect(months).toHaveLength(1); + expect(months[0]?.[0]?.date.toString()).toBe('2023-06-01'); + }); + }); From 4d71196c9f48ff06bedfec7e9d25a6fadc52347b Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 2 Jul 2024 00:31:23 +0200 Subject: [PATCH 107/128] refactor: update CalendarCore tests to mock current date and time --- packages/time/src/tests/calendar-core.test.ts | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index a1b0929..4dc548d 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -11,6 +11,31 @@ describe('CalendarCore', () => { const mockTimeZone = 'America/New_York'; beforeEach(() => { + vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate); + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(mockDateTime); + vi.spyOn(Temporal.Now, 'zonedDateTime').mockReturnValue( + Temporal.ZonedDateTime.from({ + timeZone: mockTimeZone, + year: 2023, + month: 6, + day: 15, + hour: 10, + minute: 0, + second: 0, + }) + ); + vi.spyOn(Temporal.Now, 'zonedDateTimeISO').mockReturnValue( + Temporal.ZonedDateTime.from({ + timeZone: mockTimeZone, + year: 2023, + month: 6, + day: 15, + hour: 10, + minute: 0, + second: 0, + }) + ); + options = { viewMode: { value: 1, unit: 'month' }, events: [ @@ -36,10 +61,6 @@ describe('CalendarCore', () => { timeZone: mockTimeZone, }; calendarCore = new CalendarCore(options); - vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate); - vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(mockDateTime); - vi.spyOn(Temporal.Now, 'zonedDateTime').mockReturnValue(Temporal.Now.zonedDateTime('gregory',mockTimeZone)); - vi.spyOn(Temporal.Now, 'zonedDateTimeISO').mockReturnValue(Temporal.Now.zonedDateTimeISO()); }); test('should initialize with the correct current period', () => { @@ -163,9 +184,10 @@ describe('CalendarCore', () => { test('should group days by weeks correctly', () => { const daysWithEvents = calendarCore.getDaysWithEvents(); const weeks = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'week' }); - expect(weeks).toHaveLength(6); + + expect(weeks).toHaveLength(5); expect(weeks[0]?.[0]?.date.toString()).toBe('2023-05-28'); - expect(weeks[4]?.[6]?.date.toString()).toBe('2023-06-30'); + expect(weeks[4]?.[6]?.date.toString()).toBe('2023-07-01'); }); test('should group days by months correctly', () => { From fbbae152bb6d915988b56db3bc1370ec1a781427 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 2 Jul 2024 17:20:33 +0200 Subject: [PATCH 108/128] refactor: Update CalendarCore to use destructuring assignment for datePart --- packages/time/src/core/calendar.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 42f23dd..f3102ce 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -212,14 +212,14 @@ export class CalendarCore< this.options.timeZone, ) splitEvents.forEach((splitEvent) => { - const splitKey = splitEvent.start.toString().split('T')[0] - if (splitKey) { - if (!map.has(splitKey)) map.set(splitKey, []) - map.get(splitKey)?.push(splitEvent) + const [ datePart ] = splitEvent.start.toString().split('T') + if (datePart) { + if (!map.has(datePart)) map.set(datePart, []) + map.get(datePart)?.push(splitEvent) } }) } else { - const eventKey = event.start.toString().split('T')[0] + const [ eventKey ] = event.start.toString().split('T') if (eventKey) { if (!map.has(eventKey)) map.set(eventKey, []) map.get(eventKey)?.push(event) From ce9b9245282754d54eae925713c1a4599bed1b98 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 2 Jul 2024 22:51:27 +0200 Subject: [PATCH 109/128] feat: add the activeDate property --- packages/time/src/calendar/types.ts | 1 + packages/time/src/core/calendar.ts | 51 ++++++++++--------- packages/time/src/tests/calendar-core.test.ts | 8 ++- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index 05933b9..1e97697 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -12,6 +12,7 @@ export interface Event { export interface CalendarStore { currentPeriod: Temporal.PlainDate + activeDate: Temporal.PlainDate viewMode: { value: number unit: 'month' | 'week' | 'day' diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index f3102ce..124bb43 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -74,7 +74,6 @@ interface CalendarActions< } | null /** Retrieves the names of the days of the week, based on the current locale. */ getDaysNames: (weekday?: 'long' | 'short') => string[] - /** Groups days by a specified unit. */ groupDaysBy: ( props: Omit, 'weekStartsOn'>, @@ -91,6 +90,8 @@ interface CalendarState< viewMode: CalendarStore['viewMode'] /** An array of days, each potentially containing events. */ days: Array> + /** The currently active date in the calendar. */ + activeDate: Temporal.PlainDate } export interface CalendarApi< @@ -121,10 +122,11 @@ export class CalendarCore< resources: options.resources || null, } + const now = Temporal.Now.plainDateISO().withCalendar(this.options.calendar) + this.store = new Store({ - currentPeriod: Temporal.Now.plainDateISO().withCalendar( - this.options.calendar, - ), + currentPeriod: now, + activeDate: now, viewMode: options.viewMode, }) } @@ -269,39 +271,39 @@ export class CalendarCore< } goToPreviousPeriod() { - const firstDayOfMonth = this.getFirstDayOfMonth() - const firstDayOfWeek = this.getFirstDayOfWeek() - switch (this.store.state.viewMode.unit) { case 'month': { - const firstDayOfPrevMonth = firstDayOfMonth.subtract({ + const newActiveDate = this.store.state.activeDate.subtract({ months: this.store.state.viewMode.value, }) this.store.setState((prev) => ({ ...prev, - currentPeriod: firstDayOfPrevMonth, + activeDate: newActiveDate, + currentPeriod: newActiveDate, })) break } case 'week': { - const firstDayOfPrevWeek = firstDayOfWeek.subtract({ + const newActiveDate = this.store.state.activeDate.subtract({ weeks: this.store.state.viewMode.value, }) this.store.setState((prev) => ({ ...prev, - currentPeriod: firstDayOfPrevWeek, + activeDate: newActiveDate, + currentPeriod: newActiveDate, })) break } case 'day': { - const prevCustomStart = this.store.state.currentPeriod.subtract({ + const newActiveDate = this.store.state.activeDate.subtract({ days: this.store.state.viewMode.value, }) this.store.setState((prev) => ({ ...prev, - currentPeriod: prevCustomStart, + activeDate: newActiveDate, + currentPeriod: newActiveDate, })) break } @@ -309,39 +311,39 @@ export class CalendarCore< } goToNextPeriod() { - const firstDayOfMonth = this.getFirstDayOfMonth() - const firstDayOfWeek = this.getFirstDayOfWeek() - switch (this.store.state.viewMode.unit) { case 'month': { - const firstDayOfNextMonth = firstDayOfMonth.add({ + const newActiveDate = this.store.state.activeDate.add({ months: this.store.state.viewMode.value, }) this.store.setState((prev) => ({ ...prev, - currentPeriod: firstDayOfNextMonth, + activeDate: newActiveDate, + currentPeriod: newActiveDate, })) break } case 'week': { - const firstDayOfNextWeek = firstDayOfWeek.add({ + const newActiveDate = this.store.state.activeDate.add({ weeks: this.store.state.viewMode.value, }) this.store.setState((prev) => ({ ...prev, - currentPeriod: firstDayOfNextWeek, + activeDate: newActiveDate, + currentPeriod: newActiveDate, })) break } case 'day': { - const nextCustomStart = this.store.state.currentPeriod.add({ + const newActiveDate = this.store.state.activeDate.add({ days: this.store.state.viewMode.value, }) this.store.setState((prev) => ({ ...prev, - currentPeriod: nextCustomStart, + activeDate: newActiveDate, + currentPeriod: newActiveDate, })) break } @@ -349,15 +351,18 @@ export class CalendarCore< } goToCurrentPeriod() { + const now = Temporal.Now.plainDateISO() this.store.setState((prev) => ({ ...prev, - currentPeriod: Temporal.Now.plainDateISO(), + activeDate: now, + currentPeriod: now, })) } goToSpecificPeriod(date: Temporal.PlainDate) { this.store.setState((prev) => ({ ...prev, + activeDate: date, currentPeriod: date, })) } diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 4dc548d..8bca208 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -66,6 +66,7 @@ describe('CalendarCore', () => { test('should initialize with the correct current period', () => { const today = Temporal.Now.plainDateISO(); expect(calendarCore.store.state.currentPeriod).toEqual(today); + expect(calendarCore.store.state.activeDate).toEqual(today); }); test('should get the correct days with events for the month', () => { @@ -95,6 +96,7 @@ describe('CalendarCore', () => { calendarCore.goToPreviousPeriod(); const expectedPreviousMonth = initialPeriod.subtract({ months: 1 }); expect(calendarCore.store.state.currentPeriod).toEqual(expectedPreviousMonth); + expect(calendarCore.store.state.activeDate).toEqual(expectedPreviousMonth); }); test('should go to next period correctly', () => { @@ -102,6 +104,7 @@ describe('CalendarCore', () => { calendarCore.goToNextPeriod(); const expectedNextMonth = initialPeriod.add({ months: 1 }); expect(calendarCore.store.state.currentPeriod).toEqual(expectedNextMonth); + expect(calendarCore.store.state.activeDate).toEqual(expectedNextMonth); }); test('should go to current period correctly', () => { @@ -109,12 +112,14 @@ describe('CalendarCore', () => { calendarCore.goToCurrentPeriod(); const today = Temporal.Now.plainDateISO(); expect(calendarCore.store.state.currentPeriod).toEqual(today); + expect(calendarCore.store.state.activeDate).toEqual(today); }); test('should go to specific period correctly', () => { const specificDate = Temporal.PlainDate.from('2023-07-01'); calendarCore.goToSpecificPeriod(specificDate); expect(calendarCore.store.state.currentPeriod).toEqual(specificDate); + expect(calendarCore.store.state.activeDate).toEqual(specificDate); }); test('should group days correctly', () => { @@ -135,6 +140,7 @@ describe('CalendarCore', () => { const today = Temporal.Now.plainDateISO(customCalendar); expect(calendarCore.store.state.currentPeriod.calendarId).toBe(customCalendar); expect(calendarCore.store.state.currentPeriod).toEqual(today); + expect(calendarCore.store.state.activeDate).toEqual(today); }); test('should return the correct props for an event', () => { @@ -179,6 +185,7 @@ describe('CalendarCore', () => { calendarCore.goToCurrentPeriod(); const today = Temporal.Now.plainDateISO(); expect(calendarCore.store.state.currentPeriod).toEqual(today); + expect(calendarCore.store.state.activeDate).toEqual(today); }); test('should group days by weeks correctly', () => { @@ -196,5 +203,4 @@ describe('CalendarCore', () => { expect(months).toHaveLength(1); expect(months[0]?.[0]?.date.toString()).toBe('2023-06-01'); }); - }); From 66bfe536de0105447c4bba142c395c78a14b0f3e Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 2 Jul 2024 23:02:58 +0200 Subject: [PATCH 110/128] refactor: use compare method --- packages/time/src/core/calendar.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 124bb43..dab7582 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -187,13 +187,21 @@ export class CalendarCore< } const allDays = generateDateRange(start, end) - const startMonth = this.store.state.currentPeriod.month - const endMonth = this.store.state.currentPeriod.add({ - months: this.store.state.viewMode.value - 1, - }).month + const startMonthDate = this.store.state.currentPeriod.with({ day: 1 }) + const endMonthDate = this.store.state.currentPeriod + .add({ + months: this.store.state.viewMode.value - 1, + }) + .with({ + day: Temporal.PlainDate.from( + this.store.state.currentPeriod.toString({ calendarName: 'auto' }), + ).daysInMonth, + }) return allDays.filter( - (day) => day.month >= startMonth && day.month <= endMonth, + (day) => + Temporal.PlainDate.compare(day, startMonthDate) >= 0 && + Temporal.PlainDate.compare(day, endMonthDate) <= 0, ) } @@ -214,14 +222,14 @@ export class CalendarCore< this.options.timeZone, ) splitEvents.forEach((splitEvent) => { - const [ datePart ] = splitEvent.start.toString().split('T') + const [datePart] = splitEvent.start.toString().split('T') if (datePart) { if (!map.has(datePart)) map.set(datePart, []) map.get(datePart)?.push(splitEvent) } }) } else { - const [ eventKey ] = event.start.toString().split('T') + const [eventKey] = event.start.toString().split('T') if (eventKey) { if (!map.has(eventKey)) map.set(eventKey, []) map.get(eventKey)?.push(event) @@ -253,14 +261,14 @@ export class CalendarCore< } getDaysNames(weekday: 'long' | 'short' = 'short') { - const baseDate = Temporal.PlainDate.from('2024-01-01'); - const firstDayOfWeek = this.getFirstDayOfWeek().dayOfWeek; + const baseDate = Temporal.PlainDate.from('2024-01-01') + const firstDayOfWeek = this.getFirstDayOfWeek().dayOfWeek return Array.from({ length: 7 }).map((_, i) => baseDate .add({ days: (i + (firstDayOfWeek - 1)) % 7 }) .toLocaleString(this.options.locale, { weekday: weekday }), - ); + ) } changeViewMode(newViewMode: CalendarStore['viewMode']) { From 06f633d2b90036f09cb43df27d9c55051496e07b Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Tue, 2 Jul 2024 23:40:07 +0200 Subject: [PATCH 111/128] feat: add workWeek unit --- packages/time/src/calendar/groupDaysBy.ts | 56 +++- packages/time/src/calendar/types.ts | 2 +- packages/time/src/core/calendar.ts | 30 +- packages/time/src/tests/calendar-core.test.ts | 274 +++++++++++------- 4 files changed, 250 insertions(+), 112 deletions(-) diff --git a/packages/time/src/calendar/groupDaysBy.ts b/packages/time/src/calendar/groupDaysBy.ts index dbd3af6..582eeba 100644 --- a/packages/time/src/calendar/groupDaysBy.ts +++ b/packages/time/src/calendar/groupDaysBy.ts @@ -21,7 +21,7 @@ type GroupDaysByWeekProps< TResource extends Resource, TEvent extends Event = Event, > = GroupDaysByBaseProps & { - unit: 'week' + unit: 'week' | 'workWeek' fillMissingDays?: boolean } @@ -115,6 +115,60 @@ export const groupDaysBy = < return weeks } + + case 'workWeek': { + const workWeeks: (Day | null)[][] = [] + let currentWorkWeek: (Day | null)[] = [] + + days.forEach((day) => { + if ( + currentWorkWeek.length === 0 && + day?.date.dayOfWeek !== weekStartsOn + ) { + if (day) { + const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7 + for (let i = 0; i < dayOfWeek; i++) { + currentWorkWeek.push( + fillMissingDays + ? { + date: day.date.subtract({ days: dayOfWeek - i }), + events: [], + isToday: false, + isInCurrentPeriod: false, + } + : null, + ) + } + } + } + currentWorkWeek.push(day) + if (currentWorkWeek.length === 5) { + workWeeks.push(currentWorkWeek) + currentWorkWeek = [] + } + }) + + if (currentWorkWeek.length > 0) { + while (currentWorkWeek.length < 5) { + const lastDate = + currentWorkWeek[currentWorkWeek.length - 1]?.date ?? + Temporal.PlainDate.from('2024-01-01') + currentWorkWeek.push( + fillMissingDays + ? { + date: lastDate.add({ days: 1 }), + events: [], + isToday: false, + isInCurrentPeriod: false, + } + : null, + ) + } + workWeeks.push(currentWorkWeek) + } + + return workWeeks + } default: break } diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index 1e97697..30c7b79 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -15,7 +15,7 @@ export interface CalendarStore { activeDate: Temporal.PlainDate viewMode: { value: number - unit: 'month' | 'week' | 'day' + unit: 'month' | 'week' | 'workWeek' | 'day' } } diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index dab7582..a274a7e 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -20,8 +20,8 @@ export type * from '../calendar/types' export interface ViewMode { /** The number of units for the view mode. */ value: number - /** The unit of time that the calendar view should display (month, week, or day). */ - unit: 'month' | 'week' | 'day' + /** The unit of time that the calendar view should display (month, week, workWeek or day). */ + unit: 'month' | 'week' | 'day' | 'workWeek' } /** @@ -184,6 +184,10 @@ export class CalendarCore< }) break } + case 'workWeek': { + end = start.add({ days: 4 }) + break + } } const allDays = generateDateRange(start, end) @@ -315,6 +319,17 @@ export class CalendarCore< })) break } + case 'workWeek': { + const newActiveDate = this.store.state.activeDate.subtract({ + days: 5, + }) + this.store.setState((prev) => ({ + ...prev, + activeDate: newActiveDate, + currentPeriod: newActiveDate, + })) + break + } } } @@ -355,6 +370,17 @@ export class CalendarCore< })) break } + case 'workWeek': { + const newActiveDate = this.store.state.activeDate.add({ + days: 5, + }) + this.store.setState((prev) => ({ + ...prev, + activeDate: newActiveDate, + currentPeriod: newActiveDate, + })) + break + } } } diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 8bca208..1ab82e8 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -1,18 +1,18 @@ -import { Temporal } from '@js-temporal/polyfill'; -import { beforeEach, describe, expect, test, vi } from 'vitest'; -import { CalendarCore } from '../core/calendar'; -import type { CalendarCoreOptions, Event } from '../core/calendar'; +import { Temporal } from '@js-temporal/polyfill' +import { beforeEach, describe, expect, test, vi } from 'vitest' +import { CalendarCore } from '../core/calendar' +import type { CalendarCoreOptions, Event } from '../core/calendar' describe('CalendarCore', () => { - let options: CalendarCoreOptions>; - let calendarCore: CalendarCore>; - const mockDate = Temporal.PlainDate.from('2023-06-15'); - const mockDateTime = Temporal.PlainDateTime.from('2023-06-15T10:00'); - const mockTimeZone = 'America/New_York'; + let options: CalendarCoreOptions> + let calendarCore: CalendarCore> + const mockDate = Temporal.PlainDate.from('2023-06-15') + const mockDateTime = Temporal.PlainDateTime.from('2023-06-15T10:00') + const mockTimeZone = 'America/New_York' beforeEach(() => { - vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate); - vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(mockDateTime); + vi.spyOn(Temporal.Now, 'plainDateISO').mockReturnValue(mockDate) + vi.spyOn(Temporal.Now, 'plainDateTimeISO').mockReturnValue(mockDateTime) vi.spyOn(Temporal.Now, 'zonedDateTime').mockReturnValue( Temporal.ZonedDateTime.from({ timeZone: mockTimeZone, @@ -22,8 +22,8 @@ describe('CalendarCore', () => { hour: 10, minute: 0, second: 0, - }) - ); + }), + ) vi.spyOn(Temporal.Now, 'zonedDateTimeISO').mockReturnValue( Temporal.ZonedDateTime.from({ timeZone: mockTimeZone, @@ -33,8 +33,8 @@ describe('CalendarCore', () => { hour: 10, minute: 0, second: 0, - }) - ); + }), + ) options = { viewMode: { value: 1, unit: 'month' }, @@ -59,148 +59,206 @@ describe('CalendarCore', () => { }, ], timeZone: mockTimeZone, - }; - calendarCore = new CalendarCore(options); - }); + } + calendarCore = new CalendarCore(options) + }) test('should initialize with the correct current period', () => { - const today = Temporal.Now.plainDateISO(); - expect(calendarCore.store.state.currentPeriod).toEqual(today); - expect(calendarCore.store.state.activeDate).toEqual(today); - }); + const today = Temporal.Now.plainDateISO() + expect(calendarCore.store.state.currentPeriod).toEqual(today) + expect(calendarCore.store.state.activeDate).toEqual(today) + }) test('should get the correct days with events for the month', () => { - const daysWithEvents = calendarCore.getDaysWithEvents(); - expect(daysWithEvents.length).toBeGreaterThan(0); - }); + const daysWithEvents = calendarCore.getDaysWithEvents() + expect(daysWithEvents.length).toBeGreaterThan(0) + }) test('should correctly map events to days', () => { - const daysWithEvents = calendarCore.getDaysWithEvents(); - const dayWithEvent1 = daysWithEvents.find((day) => day.date.equals(Temporal.PlainDate.from('2023-06-10'))); - const dayWithEvent2 = daysWithEvents.find((day) => day.date.equals(Temporal.PlainDate.from('2023-06-12'))); - expect(dayWithEvent1?.events).toHaveLength(1); - expect(dayWithEvent1?.events[0]?.id).toBe('1'); - expect(dayWithEvent2?.events).toHaveLength(2); - expect(dayWithEvent2?.events[0]?.id).toBe('2'); - expect(dayWithEvent2?.events[1]?.id).toBe('3'); - }); + const daysWithEvents = calendarCore.getDaysWithEvents() + const dayWithEvent1 = daysWithEvents.find((day) => + day.date.equals(Temporal.PlainDate.from('2023-06-10')), + ) + const dayWithEvent2 = daysWithEvents.find((day) => + day.date.equals(Temporal.PlainDate.from('2023-06-12')), + ) + expect(dayWithEvent1?.events).toHaveLength(1) + expect(dayWithEvent1?.events[0]?.id).toBe('1') + expect(dayWithEvent2?.events).toHaveLength(2) + expect(dayWithEvent2?.events[0]?.id).toBe('2') + expect(dayWithEvent2?.events[1]?.id).toBe('3') + }) test('should change view mode correctly', () => { - calendarCore.changeViewMode({ value: 2, unit: 'week' }); - expect(calendarCore.store.state.viewMode.value).toBe(2); - expect(calendarCore.store.state.viewMode.unit).toBe('week'); - }); + calendarCore.changeViewMode({ value: 2, unit: 'week' }) + expect(calendarCore.store.state.viewMode.value).toBe(2) + expect(calendarCore.store.state.viewMode.unit).toBe('week') + }) test('should go to previous period correctly', () => { - const initialPeriod = calendarCore.store.state.currentPeriod; - calendarCore.goToPreviousPeriod(); - const expectedPreviousMonth = initialPeriod.subtract({ months: 1 }); - expect(calendarCore.store.state.currentPeriod).toEqual(expectedPreviousMonth); - expect(calendarCore.store.state.activeDate).toEqual(expectedPreviousMonth); - }); + const initialPeriod = calendarCore.store.state.currentPeriod + calendarCore.goToPreviousPeriod() + const expectedPreviousMonth = initialPeriod.subtract({ months: 1 }) + expect(calendarCore.store.state.currentPeriod).toEqual( + expectedPreviousMonth, + ) + expect(calendarCore.store.state.activeDate).toEqual(expectedPreviousMonth) + }) test('should go to next period correctly', () => { - const initialPeriod = calendarCore.store.state.currentPeriod; - calendarCore.goToNextPeriod(); - const expectedNextMonth = initialPeriod.add({ months: 1 }); - expect(calendarCore.store.state.currentPeriod).toEqual(expectedNextMonth); - expect(calendarCore.store.state.activeDate).toEqual(expectedNextMonth); - }); + const initialPeriod = calendarCore.store.state.currentPeriod + calendarCore.goToNextPeriod() + const expectedNextMonth = initialPeriod.add({ months: 1 }) + expect(calendarCore.store.state.currentPeriod).toEqual(expectedNextMonth) + expect(calendarCore.store.state.activeDate).toEqual(expectedNextMonth) + }) test('should go to current period correctly', () => { - calendarCore.goToNextPeriod(); - calendarCore.goToCurrentPeriod(); - const today = Temporal.Now.plainDateISO(); - expect(calendarCore.store.state.currentPeriod).toEqual(today); - expect(calendarCore.store.state.activeDate).toEqual(today); - }); + calendarCore.goToNextPeriod() + calendarCore.goToCurrentPeriod() + const today = Temporal.Now.plainDateISO() + expect(calendarCore.store.state.currentPeriod).toEqual(today) + expect(calendarCore.store.state.activeDate).toEqual(today) + }) test('should go to specific period correctly', () => { - const specificDate = Temporal.PlainDate.from('2023-07-01'); - calendarCore.goToSpecificPeriod(specificDate); - expect(calendarCore.store.state.currentPeriod).toEqual(specificDate); - expect(calendarCore.store.state.activeDate).toEqual(specificDate); - }); + const specificDate = Temporal.PlainDate.from('2023-07-01') + calendarCore.goToSpecificPeriod(specificDate) + expect(calendarCore.store.state.currentPeriod).toEqual(specificDate) + expect(calendarCore.store.state.activeDate).toEqual(specificDate) + }) test('should group days correctly', () => { - const daysWithEvents = calendarCore.getDaysWithEvents(); - const groupedDays = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'month' }); - expect(groupedDays.length).toBeGreaterThan(0); - }); + const daysWithEvents = calendarCore.getDaysWithEvents() + const groupedDays = calendarCore.groupDaysBy({ + days: daysWithEvents, + unit: 'month', + }) + expect(groupedDays.length).toBeGreaterThan(0) + }) test('should initialize with the correct time zone', () => { - expect(calendarCore.options.timeZone).toBe(mockTimeZone); - }); + expect(calendarCore.options.timeZone).toBe(mockTimeZone) + }) test('should respect custom calendar', () => { - const customCalendar = 'islamic-civil'; - options.calendar = customCalendar; - calendarCore = new CalendarCore(options); + const customCalendar = 'islamic-civil' + options.calendar = customCalendar + calendarCore = new CalendarCore(options) - const today = Temporal.Now.plainDateISO(customCalendar); - expect(calendarCore.store.state.currentPeriod.calendarId).toBe(customCalendar); - expect(calendarCore.store.state.currentPeriod).toEqual(today); - expect(calendarCore.store.state.activeDate).toEqual(today); - }); + const today = Temporal.Now.plainDateISO(customCalendar) + expect(calendarCore.store.state.currentPeriod.calendarId).toBe( + customCalendar, + ) + expect(calendarCore.store.state.currentPeriod).toEqual(today) + expect(calendarCore.store.state.activeDate).toEqual(today) + }) test('should return the correct props for an event', () => { - const eventProps = calendarCore.getEventProps('1'); + const eventProps = calendarCore.getEventProps('1') expect(eventProps).toEqual({ eventHeightInMinutes: 60, isSplitEvent: false, overlappingEvents: [], - }); - }); + }) + }) test('should return the correct props for overlapping events', () => { - const event1Props = calendarCore.getEventProps('2'); - const event2Props = calendarCore.getEventProps('3'); + const event1Props = calendarCore.getEventProps('2') + const event2Props = calendarCore.getEventProps('3') expect(event1Props).toEqual({ eventHeightInMinutes: 60, isSplitEvent: false, overlappingEvents: [options.events![2]], - }); + }) expect(event2Props).toEqual({ eventHeightInMinutes: 120, isSplitEvent: false, overlappingEvents: [options.events![1]], - }); - }); + }) + }) test('should correctly mark days as in current period', () => { - const daysWithEvents = calendarCore.getDaysWithEvents(); - const currentPeriodDays = daysWithEvents.filter(day => day.isInCurrentPeriod); - expect(currentPeriodDays).toHaveLength(30); // Assuming a 30-day month - }); + const daysWithEvents = calendarCore.getDaysWithEvents() + const currentPeriodDays = daysWithEvents.filter( + (day) => day.isInCurrentPeriod, + ) + expect(currentPeriodDays).toHaveLength(30) // Assuming a 30-day month + }) test('should return the correct day names based on the locale', () => { - const daysNames = calendarCore.getDaysNames('short'); - expect(daysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']); - }); + const daysNames = calendarCore.getDaysNames('short') + expect(daysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']) + }) test('should reset to the current period correctly', () => { - calendarCore.goToNextPeriod(); - calendarCore.goToCurrentPeriod(); - const today = Temporal.Now.plainDateISO(); - expect(calendarCore.store.state.currentPeriod).toEqual(today); - expect(calendarCore.store.state.activeDate).toEqual(today); - }); + calendarCore.goToNextPeriod() + calendarCore.goToCurrentPeriod() + const today = Temporal.Now.plainDateISO() + expect(calendarCore.store.state.currentPeriod).toEqual(today) + expect(calendarCore.store.state.activeDate).toEqual(today) + }) test('should group days by weeks correctly', () => { - const daysWithEvents = calendarCore.getDaysWithEvents(); - const weeks = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'week' }); + const daysWithEvents = calendarCore.getDaysWithEvents() + const weeks = calendarCore.groupDaysBy({ + days: daysWithEvents, + unit: 'week', + }) - expect(weeks).toHaveLength(5); - expect(weeks[0]?.[0]?.date.toString()).toBe('2023-05-28'); - expect(weeks[4]?.[6]?.date.toString()).toBe('2023-07-01'); - }); + expect(weeks).toHaveLength(5) + expect(weeks[0]?.[0]?.date.toString()).toBe('2023-05-28') + expect(weeks[4]?.[6]?.date.toString()).toBe('2023-07-01') + }) test('should group days by months correctly', () => { - const daysWithEvents = calendarCore.getDaysWithEvents(); - const months = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'month' }); - expect(months).toHaveLength(1); - expect(months[0]?.[0]?.date.toString()).toBe('2023-06-01'); - }); -}); + const daysWithEvents = calendarCore.getDaysWithEvents() + const months = calendarCore.groupDaysBy({ + days: daysWithEvents, + unit: 'month', + }) + expect(months).toHaveLength(1) + expect(months[0]?.[0]?.date.toString()).toBe('2023-06-01') + }) + + test('should change view mode to workWeek correctly', () => { + calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) + expect(calendarCore.store.state.viewMode.value).toBe(1) + expect(calendarCore.store.state.viewMode.unit).toBe('workWeek') + }) + + test('should go to previous workWeek correctly', () => { + calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) + const initialPeriod = calendarCore.store.state.currentPeriod + calendarCore.goToPreviousPeriod() + const expectedPreviousWorkWeek = initialPeriod.subtract({ days: 5 }) + expect(calendarCore.store.state.currentPeriod).toEqual( + expectedPreviousWorkWeek, + ) + expect(calendarCore.store.state.activeDate).toEqual( + expectedPreviousWorkWeek, + ) + }) + + test('should go to next workWeek correctly', () => { + calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) + const initialPeriod = calendarCore.store.state.currentPeriod + calendarCore.goToNextPeriod() + const expectedNextWorkWeek = initialPeriod.add({ days: 5 }) + expect(calendarCore.store.state.currentPeriod).toEqual(expectedNextWorkWeek) + expect(calendarCore.store.state.activeDate).toEqual(expectedNextWorkWeek) + }) + + test('should group days by workWeek correctly', () => { + const daysWithEvents = calendarCore.getDaysWithEvents() + const workWeeks = calendarCore.groupDaysBy({ + days: daysWithEvents, + unit: 'workWeek', + }) + + expect(workWeeks.length).toBeGreaterThan(0) + expect(workWeeks[0]?.length).toBe(5) + }) +}) From 0c8c868388f19dadd456482c27b83aa015f50a9a Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 3 Jul 2024 00:00:46 +0200 Subject: [PATCH 112/128] feat: add workWeek unit --- packages/time/src/calendar/groupDaysBy.ts | 142 ++++++++++-------- packages/time/src/core/calendar.ts | 3 +- packages/time/src/tests/calendar-core.test.ts | 41 ++--- 3 files changed, 100 insertions(+), 86 deletions(-) diff --git a/packages/time/src/calendar/groupDaysBy.ts b/packages/time/src/calendar/groupDaysBy.ts index 582eeba..b739c34 100644 --- a/packages/time/src/calendar/groupDaysBy.ts +++ b/packages/time/src/calendar/groupDaysBy.ts @@ -1,28 +1,29 @@ -import { Temporal } from '@js-temporal/polyfill' -import type { Day, Event, Resource } from './types' +import { Temporal } from '@js-temporal/polyfill'; +import type { Day, Event, Resource } from './types'; interface GroupDaysByBaseProps< TResource extends Resource, TEvent extends Event = Event, > { - days: (Day | null)[] - weekStartsOn: number + days: (Day | null)[]; + weekStartsOn: number; + locale: string; } type GroupDaysByMonthProps< TResource extends Resource, TEvent extends Event = Event, > = GroupDaysByBaseProps & { - unit: 'month' - fillMissingDays?: never + unit: 'month'; + fillMissingDays?: never; } type GroupDaysByWeekProps< TResource extends Resource, TEvent extends Event = Event, > = GroupDaysByBaseProps & { - unit: 'week' | 'workWeek' - fillMissingDays?: boolean + unit: 'week' | 'workWeek'; + fillMissingDays?: boolean; } export type GroupDaysByProps< @@ -30,7 +31,7 @@ export type GroupDaysByProps< TEvent extends Event = Event, > = | GroupDaysByMonthProps - | GroupDaysByWeekProps + | GroupDaysByWeekProps; export const groupDaysBy = < TResource extends Resource, @@ -40,39 +41,42 @@ export const groupDaysBy = < unit, fillMissingDays = true, weekStartsOn, + locale, }: GroupDaysByProps): (Day< TResource, TEvent > | null)[][] => { - const groups: (Day | null)[][] = [] + const groups: (Day | null)[][] = []; + const loc = new Intl.Locale(locale); + const { weekend } = loc.getWeekInfo(); switch (unit) { case 'month': { - let currentMonth: (Day | null)[] = [] + let currentMonth: (Day | null)[] = []; days.forEach((day) => { if ( currentMonth.length > 0 && day?.date.month !== currentMonth[0]?.date.month ) { - groups.push(currentMonth) - currentMonth = [] + groups.push(currentMonth); + currentMonth = []; } - currentMonth.push(day) - }) + currentMonth.push(day); + }); if (currentMonth.length > 0) { - groups.push(currentMonth) + groups.push(currentMonth); } - break + break; } case 'week': { - const weeks: (Day | null)[][] = [] - let currentWeek: (Day | null)[] = [] + const weeks: (Day | null)[][] = []; + let currentWeek: (Day | null)[] = []; days.forEach((day) => { if (currentWeek.length === 0 && day?.date.dayOfWeek !== weekStartsOn) { if (day) { - const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7 + const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7; for (let i = 0; i < dayOfWeek; i++) { currentWeek.push( fillMissingDays @@ -83,22 +87,22 @@ export const groupDaysBy = < isInCurrentPeriod: false, } : null, - ) + ); } } } - currentWeek.push(day) + currentWeek.push(day); if (currentWeek.length === 7) { - weeks.push(currentWeek) - currentWeek = [] + weeks.push(currentWeek); + currentWeek = []; } - }) + }); if (currentWeek.length > 0) { while (currentWeek.length < 7) { const lastDate = currentWeek[currentWeek.length - 1]?.date ?? - Temporal.PlainDate.from('2024-01-01') + Temporal.PlainDate.from('2024-01-01'); currentWeek.push( fillMissingDays ? { @@ -108,69 +112,75 @@ export const groupDaysBy = < isInCurrentPeriod: false, } : null, - ) + ); } - weeks.push(currentWeek) + weeks.push(currentWeek); } - return weeks + return weeks; } case 'workWeek': { - const workWeeks: (Day | null)[][] = [] - let currentWorkWeek: (Day | null)[] = [] + const workWeeks: (Day | null)[][] = []; + let currentWorkWeek: (Day | null)[] = []; days.forEach((day) => { - if ( - currentWorkWeek.length === 0 && - day?.date.dayOfWeek !== weekStartsOn - ) { + if (currentWorkWeek.length === 0 && day?.date.dayOfWeek !== weekStartsOn) { if (day) { - const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7 + const dayOfWeek = (day.date.dayOfWeek - weekStartsOn + 7) % 7; for (let i = 0; i < dayOfWeek; i++) { - currentWorkWeek.push( - fillMissingDays - ? { - date: day.date.subtract({ days: dayOfWeek - i }), - events: [], - isToday: false, - isInCurrentPeriod: false, - } - : null, - ) + const newDay = day.date.subtract({ days: dayOfWeek - i }); + if (!weekend.includes(newDay.dayOfWeek)) { + currentWorkWeek.push( + fillMissingDays + ? { + date: newDay, + events: [], + isToday: false, + isInCurrentPeriod: false, + } + : null, + ); + } } } } - currentWorkWeek.push(day) + if (day && !weekend.includes(day.date.dayOfWeek)) { + currentWorkWeek.push(day); + } if (currentWorkWeek.length === 5) { - workWeeks.push(currentWorkWeek) - currentWorkWeek = [] + workWeeks.push(currentWorkWeek); + currentWorkWeek = []; } - }) + }); if (currentWorkWeek.length > 0) { while (currentWorkWeek.length < 5) { const lastDate = currentWorkWeek[currentWorkWeek.length - 1]?.date ?? - Temporal.PlainDate.from('2024-01-01') - currentWorkWeek.push( - fillMissingDays - ? { - date: lastDate.add({ days: 1 }), - events: [], - isToday: false, - isInCurrentPeriod: false, - } - : null, - ) + Temporal.PlainDate.from('2024-01-01'); + const nextDate = lastDate.add({ days: 1 }); + if (!weekend.includes(nextDate.dayOfWeek)) { + currentWorkWeek.push( + fillMissingDays + ? { + date: nextDate, + events: [], + isToday: false, + isInCurrentPeriod: false, + } + : null, + ); + } } - workWeeks.push(currentWorkWeek) + workWeeks.push(currentWorkWeek); } - return workWeeks + return workWeeks; } + default: - break + break; } - return groups -} + return groups; +}; diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index a274a7e..cf6553a 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -413,12 +413,13 @@ export class CalendarCore< days, unit, fillMissingDays = true, - }: Omit, 'weekStartsOn'>) { + }: Omit, 'weekStartsOn' | 'locale'>) { return groupDaysBy({ days, unit, fillMissingDays, weekStartsOn: this.getFirstDayOfWeek().dayOfWeek, + locale: this.options.locale, } as GroupDaysByProps) } } diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 1ab82e8..5578020 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -6,8 +6,8 @@ import type { CalendarCoreOptions, Event } from '../core/calendar' describe('CalendarCore', () => { let options: CalendarCoreOptions> let calendarCore: CalendarCore> - const mockDate = Temporal.PlainDate.from('2023-06-15') - const mockDateTime = Temporal.PlainDateTime.from('2023-06-15T10:00') + const mockDate = Temporal.PlainDate.from('2024-06-15') + const mockDateTime = Temporal.PlainDateTime.from('2024-06-15T10:00') const mockTimeZone = 'America/New_York' beforeEach(() => { @@ -16,7 +16,7 @@ describe('CalendarCore', () => { vi.spyOn(Temporal.Now, 'zonedDateTime').mockReturnValue( Temporal.ZonedDateTime.from({ timeZone: mockTimeZone, - year: 2023, + year: 2024, month: 6, day: 15, hour: 10, @@ -27,7 +27,7 @@ describe('CalendarCore', () => { vi.spyOn(Temporal.Now, 'zonedDateTimeISO').mockReturnValue( Temporal.ZonedDateTime.from({ timeZone: mockTimeZone, - year: 2023, + year: 2024, month: 6, day: 15, hour: 10, @@ -41,20 +41,20 @@ describe('CalendarCore', () => { events: [ { id: '1', - start: Temporal.PlainDateTime.from('2023-06-10T09:00'), - end: Temporal.PlainDateTime.from('2023-06-10T10:00'), + start: Temporal.PlainDateTime.from('2024-06-10T09:00'), + end: Temporal.PlainDateTime.from('2024-06-10T10:00'), title: 'Event 1', }, { id: '2', - start: Temporal.PlainDateTime.from('2023-06-12T11:00'), - end: Temporal.PlainDateTime.from('2023-06-12T12:00'), + start: Temporal.PlainDateTime.from('2024-06-12T11:00'), + end: Temporal.PlainDateTime.from('2024-06-12T12:00'), title: 'Event 2', }, { id: '3', - start: Temporal.PlainDateTime.from('2023-06-12T11:00'), - end: Temporal.PlainDateTime.from('2023-06-12T13:00'), + start: Temporal.PlainDateTime.from('2024-06-12T11:00'), + end: Temporal.PlainDateTime.from('2024-06-12T13:00'), title: 'Event 3', }, ], @@ -77,10 +77,10 @@ describe('CalendarCore', () => { test('should correctly map events to days', () => { const daysWithEvents = calendarCore.getDaysWithEvents() const dayWithEvent1 = daysWithEvents.find((day) => - day.date.equals(Temporal.PlainDate.from('2023-06-10')), + day.date.equals(Temporal.PlainDate.from('2024-06-10')), ) const dayWithEvent2 = daysWithEvents.find((day) => - day.date.equals(Temporal.PlainDate.from('2023-06-12')), + day.date.equals(Temporal.PlainDate.from('2024-06-12')), ) expect(dayWithEvent1?.events).toHaveLength(1) expect(dayWithEvent1?.events[0]?.id).toBe('1') @@ -122,7 +122,7 @@ describe('CalendarCore', () => { }) test('should go to specific period correctly', () => { - const specificDate = Temporal.PlainDate.from('2023-07-01') + const specificDate = Temporal.PlainDate.from('2024-07-01') calendarCore.goToSpecificPeriod(specificDate) expect(calendarCore.store.state.currentPeriod).toEqual(specificDate) expect(calendarCore.store.state.activeDate).toEqual(specificDate) @@ -185,7 +185,7 @@ describe('CalendarCore', () => { const currentPeriodDays = daysWithEvents.filter( (day) => day.isInCurrentPeriod, ) - expect(currentPeriodDays).toHaveLength(30) // Assuming a 30-day month + expect(currentPeriodDays).toHaveLength(30) }) test('should return the correct day names based on the locale', () => { @@ -209,8 +209,8 @@ describe('CalendarCore', () => { }) expect(weeks).toHaveLength(5) - expect(weeks[0]?.[0]?.date.toString()).toBe('2023-05-28') - expect(weeks[4]?.[6]?.date.toString()).toBe('2023-07-01') + expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-28') + expect(weeks[4]?.[6]?.date.toString()).toBe('2024-07-01') }) test('should group days by months correctly', () => { @@ -220,7 +220,7 @@ describe('CalendarCore', () => { unit: 'month', }) expect(months).toHaveLength(1) - expect(months[0]?.[0]?.date.toString()).toBe('2023-06-01') + expect(months[0]?.[0]?.date.toString()).toBe('2024-06-01') }) test('should change view mode to workWeek correctly', () => { @@ -233,7 +233,7 @@ describe('CalendarCore', () => { calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) const initialPeriod = calendarCore.store.state.currentPeriod calendarCore.goToPreviousPeriod() - const expectedPreviousWorkWeek = initialPeriod.subtract({ days: 5 }) + const expectedPreviousWorkWeek = initialPeriod.subtract({ days: 7 }) expect(calendarCore.store.state.currentPeriod).toEqual( expectedPreviousWorkWeek, ) @@ -246,12 +246,13 @@ describe('CalendarCore', () => { calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) const initialPeriod = calendarCore.store.state.currentPeriod calendarCore.goToNextPeriod() - const expectedNextWorkWeek = initialPeriod.add({ days: 5 }) + const expectedNextWorkWeek = initialPeriod.add({ days: 7 }) expect(calendarCore.store.state.currentPeriod).toEqual(expectedNextWorkWeek) expect(calendarCore.store.state.activeDate).toEqual(expectedNextWorkWeek) }) test('should group days by workWeek correctly', () => { + calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) const daysWithEvents = calendarCore.getDaysWithEvents() const workWeeks = calendarCore.groupDaysBy({ days: daysWithEvents, @@ -260,5 +261,7 @@ describe('CalendarCore', () => { expect(workWeeks.length).toBeGreaterThan(0) expect(workWeeks[0]?.length).toBe(5) + expect(workWeeks[0]?.[0]?.date.toString()).toBe('2024-06-10[u-ca=gregory]') + expect(workWeeks[0]?.[4]?.date.toString()).toBe('2024-06-14[u-ca=gregory]') }) }) From 4b7ea40bb7f1e58b57f6c8246f30c5ea7735af99 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 3 Jul 2024 00:24:31 +0200 Subject: [PATCH 113/128] test: tests reorganisation --- packages/time/src/tests/calendar-core.test.ts | 366 ++++++++++-------- 1 file changed, 203 insertions(+), 163 deletions(-) diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 5578020..4f6a81e 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -63,205 +63,245 @@ describe('CalendarCore', () => { calendarCore = new CalendarCore(options) }) - test('should initialize with the correct current period', () => { - const today = Temporal.Now.plainDateISO() - expect(calendarCore.store.state.currentPeriod).toEqual(today) - expect(calendarCore.store.state.activeDate).toEqual(today) - }) + describe('Initialization', () => { + test('should initialize with the correct current period', () => { + const today = Temporal.Now.plainDateISO() + expect(calendarCore.store.state.currentPeriod).toEqual(today) + expect(calendarCore.store.state.activeDate).toEqual(today) + }) - test('should get the correct days with events for the month', () => { - const daysWithEvents = calendarCore.getDaysWithEvents() - expect(daysWithEvents.length).toBeGreaterThan(0) - }) + test('should initialize with the correct time zone', () => { + expect(calendarCore.options.timeZone).toBe(mockTimeZone) + }) - test('should correctly map events to days', () => { - const daysWithEvents = calendarCore.getDaysWithEvents() - const dayWithEvent1 = daysWithEvents.find((day) => - day.date.equals(Temporal.PlainDate.from('2024-06-10')), - ) - const dayWithEvent2 = daysWithEvents.find((day) => - day.date.equals(Temporal.PlainDate.from('2024-06-12')), - ) - expect(dayWithEvent1?.events).toHaveLength(1) - expect(dayWithEvent1?.events[0]?.id).toBe('1') - expect(dayWithEvent2?.events).toHaveLength(2) - expect(dayWithEvent2?.events[0]?.id).toBe('2') - expect(dayWithEvent2?.events[1]?.id).toBe('3') - }) + test('should respect custom calendar', () => { + const customCalendar = 'islamic-civil' + options.calendar = customCalendar + calendarCore = new CalendarCore(options) - test('should change view mode correctly', () => { - calendarCore.changeViewMode({ value: 2, unit: 'week' }) - expect(calendarCore.store.state.viewMode.value).toBe(2) - expect(calendarCore.store.state.viewMode.unit).toBe('week') + const today = Temporal.Now.plainDateISO(customCalendar) + expect(calendarCore.store.state.currentPeriod.calendarId).toBe( + customCalendar, + ) + expect(calendarCore.store.state.currentPeriod).toEqual(today) + expect(calendarCore.store.state.activeDate).toEqual(today) + }) }) - test('should go to previous period correctly', () => { - const initialPeriod = calendarCore.store.state.currentPeriod - calendarCore.goToPreviousPeriod() - const expectedPreviousMonth = initialPeriod.subtract({ months: 1 }) - expect(calendarCore.store.state.currentPeriod).toEqual( - expectedPreviousMonth, - ) - expect(calendarCore.store.state.activeDate).toEqual(expectedPreviousMonth) - }) + describe('Event mapping', () => { + test('should get the correct days with events for the month', () => { + const daysWithEvents = calendarCore.getDaysWithEvents() + expect(daysWithEvents.length).toBeGreaterThan(0) + }) - test('should go to next period correctly', () => { - const initialPeriod = calendarCore.store.state.currentPeriod - calendarCore.goToNextPeriod() - const expectedNextMonth = initialPeriod.add({ months: 1 }) - expect(calendarCore.store.state.currentPeriod).toEqual(expectedNextMonth) - expect(calendarCore.store.state.activeDate).toEqual(expectedNextMonth) - }) + test('should correctly map events to days', () => { + const daysWithEvents = calendarCore.getDaysWithEvents() + const dayWithEvent1 = daysWithEvents.find((day) => + day.date.equals(Temporal.PlainDate.from('2024-06-10')), + ) + const dayWithEvent2 = daysWithEvents.find((day) => + day.date.equals(Temporal.PlainDate.from('2024-06-12')), + ) + expect(dayWithEvent1?.events).toHaveLength(1) + expect(dayWithEvent1?.events[0]?.id).toBe('1') + expect(dayWithEvent2?.events).toHaveLength(2) + expect(dayWithEvent2?.events[0]?.id).toBe('2') + expect(dayWithEvent2?.events[1]?.id).toBe('3') + }) - test('should go to current period correctly', () => { - calendarCore.goToNextPeriod() - calendarCore.goToCurrentPeriod() - const today = Temporal.Now.plainDateISO() - expect(calendarCore.store.state.currentPeriod).toEqual(today) - expect(calendarCore.store.state.activeDate).toEqual(today) - }) + test('should return the correct props for an event', () => { + const eventProps = calendarCore.getEventProps('1') + expect(eventProps).toEqual({ + eventHeightInMinutes: 60, + isSplitEvent: false, + overlappingEvents: [], + }) + }) - test('should go to specific period correctly', () => { - const specificDate = Temporal.PlainDate.from('2024-07-01') - calendarCore.goToSpecificPeriod(specificDate) - expect(calendarCore.store.state.currentPeriod).toEqual(specificDate) - expect(calendarCore.store.state.activeDate).toEqual(specificDate) - }) + test('should return the correct props for overlapping events', () => { + const event1Props = calendarCore.getEventProps('2') + const event2Props = calendarCore.getEventProps('3') - test('should group days correctly', () => { - const daysWithEvents = calendarCore.getDaysWithEvents() - const groupedDays = calendarCore.groupDaysBy({ - days: daysWithEvents, - unit: 'month', + expect(event1Props).toEqual({ + eventHeightInMinutes: 60, + isSplitEvent: false, + overlappingEvents: [options.events![2]], + }) + + expect(event2Props).toEqual({ + eventHeightInMinutes: 120, + isSplitEvent: false, + overlappingEvents: [options.events![1]], + }) }) - expect(groupedDays.length).toBeGreaterThan(0) }) - test('should initialize with the correct time zone', () => { - expect(calendarCore.options.timeZone).toBe(mockTimeZone) + describe('View mode', () => { + test('should change view mode correctly', () => { + calendarCore.changeViewMode({ value: 2, unit: 'week' }) + expect(calendarCore.store.state.viewMode.value).toBe(2) + expect(calendarCore.store.state.viewMode.unit).toBe('week') + }) + + test('should change view mode to workWeek correctly', () => { + calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) + expect(calendarCore.store.state.viewMode.value).toBe(1) + expect(calendarCore.store.state.viewMode.unit).toBe('workWeek') + }) }) - test('should respect custom calendar', () => { - const customCalendar = 'islamic-civil' - options.calendar = customCalendar - calendarCore = new CalendarCore(options) + describe('Navigation', () => { + test('should go to previous period correctly', () => { + const initialPeriod = calendarCore.store.state.currentPeriod + calendarCore.goToPreviousPeriod() + const expectedPreviousMonth = initialPeriod.subtract({ months: 1 }) + expect(calendarCore.store.state.currentPeriod).toEqual( + expectedPreviousMonth, + ) + expect(calendarCore.store.state.activeDate).toEqual(expectedPreviousMonth) + }) - const today = Temporal.Now.plainDateISO(customCalendar) - expect(calendarCore.store.state.currentPeriod.calendarId).toBe( - customCalendar, - ) - expect(calendarCore.store.state.currentPeriod).toEqual(today) - expect(calendarCore.store.state.activeDate).toEqual(today) - }) + test('should go to next period correctly', () => { + const initialPeriod = calendarCore.store.state.currentPeriod + calendarCore.goToNextPeriod() + const expectedNextMonth = initialPeriod.add({ months: 1 }) + expect(calendarCore.store.state.currentPeriod).toEqual(expectedNextMonth) + expect(calendarCore.store.state.activeDate).toEqual(expectedNextMonth) + }) - test('should return the correct props for an event', () => { - const eventProps = calendarCore.getEventProps('1') - expect(eventProps).toEqual({ - eventHeightInMinutes: 60, - isSplitEvent: false, - overlappingEvents: [], + test('should go to current period correctly', () => { + calendarCore.goToNextPeriod() + calendarCore.goToCurrentPeriod() + const today = Temporal.Now.plainDateISO() + expect(calendarCore.store.state.currentPeriod).toEqual(today) + expect(calendarCore.store.state.activeDate).toEqual(today) }) - }) - test('should return the correct props for overlapping events', () => { - const event1Props = calendarCore.getEventProps('2') - const event2Props = calendarCore.getEventProps('3') + test('should go to specific period correctly', () => { + const specificDate = Temporal.PlainDate.from('2024-07-01') + calendarCore.goToSpecificPeriod(specificDate) + expect(calendarCore.store.state.currentPeriod).toEqual(specificDate) + expect(calendarCore.store.state.activeDate).toEqual(specificDate) + }) - expect(event1Props).toEqual({ - eventHeightInMinutes: 60, - isSplitEvent: false, - overlappingEvents: [options.events![2]], + test('should go to previous workWeek correctly', () => { + calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) + const initialPeriod = calendarCore.store.state.currentPeriod + calendarCore.goToPreviousPeriod() + const expectedPreviousWorkWeek = initialPeriod.subtract({ days: 7 }) + expect(calendarCore.store.state.currentPeriod).toEqual( + expectedPreviousWorkWeek, + ) + expect(calendarCore.store.state.activeDate).toEqual( + expectedPreviousWorkWeek, + ) }) - expect(event2Props).toEqual({ - eventHeightInMinutes: 120, - isSplitEvent: false, - overlappingEvents: [options.events![1]], + test('should go to next workWeek correctly', () => { + calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) + const initialPeriod = calendarCore.store.state.currentPeriod + calendarCore.goToNextPeriod() + const expectedNextWorkWeek = initialPeriod.add({ days: 7 }) + expect(calendarCore.store.state.currentPeriod).toEqual( + expectedNextWorkWeek, + ) + expect(calendarCore.store.state.activeDate).toEqual(expectedNextWorkWeek) }) }) - test('should correctly mark days as in current period', () => { - const daysWithEvents = calendarCore.getDaysWithEvents() - const currentPeriodDays = daysWithEvents.filter( - (day) => day.isInCurrentPeriod, - ) - expect(currentPeriodDays).toHaveLength(30) - }) + describe('Days grouping', () => { + test('should group days correctly', () => { + const daysWithEvents = calendarCore.getDaysWithEvents() + const groupedDays = calendarCore.groupDaysBy({ + days: daysWithEvents, + unit: 'month', + }) + expect(groupedDays.length).toBeGreaterThan(0) + }) - test('should return the correct day names based on the locale', () => { - const daysNames = calendarCore.getDaysNames('short') - expect(daysNames).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']) - }) + test('should group days by weeks correctly', () => { + const daysWithEvents = calendarCore.getDaysWithEvents() + const weeks = calendarCore.groupDaysBy({ + days: daysWithEvents, + unit: 'week', + }) - test('should reset to the current period correctly', () => { - calendarCore.goToNextPeriod() - calendarCore.goToCurrentPeriod() - const today = Temporal.Now.plainDateISO() - expect(calendarCore.store.state.currentPeriod).toEqual(today) - expect(calendarCore.store.state.activeDate).toEqual(today) - }) + expect(weeks).toHaveLength(6) + expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-26') + expect(weeks[5]?.[6]?.date.toString()).toBe('2024-07-06') + }) - test('should group days by weeks correctly', () => { - const daysWithEvents = calendarCore.getDaysWithEvents() - const weeks = calendarCore.groupDaysBy({ - days: daysWithEvents, - unit: 'week', + test('should group days by months correctly', () => { + const daysWithEvents = calendarCore.getDaysWithEvents() + const months = calendarCore.groupDaysBy({ + days: daysWithEvents, + unit: 'month', + }) + expect(months).toHaveLength(1) + expect(months[0]?.[0]?.date.toString()).toBe('2024-06-01') }) - expect(weeks).toHaveLength(5) - expect(weeks[0]?.[0]?.date.toString()).toBe('2024-05-28') - expect(weeks[4]?.[6]?.date.toString()).toBe('2024-07-01') - }) + test('should group days by workWeek correctly', () => { + calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) + const daysWithEvents = calendarCore.getDaysWithEvents() + const workWeeks = calendarCore.groupDaysBy({ + days: daysWithEvents, + unit: 'workWeek', + }) - test('should group days by months correctly', () => { - const daysWithEvents = calendarCore.getDaysWithEvents() - const months = calendarCore.groupDaysBy({ - days: daysWithEvents, - unit: 'month', + expect(workWeeks.length).toBeGreaterThan(0) + expect(workWeeks[0]?.length).toBe(5) + expect(workWeeks[0]?.[0]?.date.toString()).toBe( + '2024-06-10[u-ca=gregory]', + ) + expect(workWeeks[0]?.[4]?.date.toString()).toBe( + '2024-06-14[u-ca=gregory]', + ) }) - expect(months).toHaveLength(1) - expect(months[0]?.[0]?.date.toString()).toBe('2024-06-01') - }) - - test('should change view mode to workWeek correctly', () => { - calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) - expect(calendarCore.store.state.viewMode.value).toBe(1) - expect(calendarCore.store.state.viewMode.unit).toBe('workWeek') - }) - test('should go to previous workWeek correctly', () => { - calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) - const initialPeriod = calendarCore.store.state.currentPeriod - calendarCore.goToPreviousPeriod() - const expectedPreviousWorkWeek = initialPeriod.subtract({ days: 7 }) - expect(calendarCore.store.state.currentPeriod).toEqual( - expectedPreviousWorkWeek, - ) - expect(calendarCore.store.state.activeDate).toEqual( - expectedPreviousWorkWeek, - ) - }) + test('should group days by workWeek correctly with custom locale', () => { + const customLocale = 'pl' + calendarCore = new CalendarCore({ ...options, locale: customLocale }) + const daysWithEvents = calendarCore.getDaysWithEvents() + const workWeeks = calendarCore.groupDaysBy({ + days: daysWithEvents, + unit: 'workWeek', + }) - test('should go to next workWeek correctly', () => { - calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) - const initialPeriod = calendarCore.store.state.currentPeriod - calendarCore.goToNextPeriod() - const expectedNextWorkWeek = initialPeriod.add({ days: 7 }) - expect(calendarCore.store.state.currentPeriod).toEqual(expectedNextWorkWeek) - expect(calendarCore.store.state.activeDate).toEqual(expectedNextWorkWeek) + expect(workWeeks.length).toBeGreaterThan(0) + expect(workWeeks[0]?.length).toBe(5) + expect(workWeeks[0]?.[0]?.date.toString()).toBe('2024-05-27') + expect(workWeeks[0]?.[4]?.date.toString()).toBe('2024-05-31') + }) }) - test('should group days by workWeek correctly', () => { - calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) - const daysWithEvents = calendarCore.getDaysWithEvents() - const workWeeks = calendarCore.groupDaysBy({ - days: daysWithEvents, - unit: 'workWeek', + describe('Locale and timezone', () => { + test('should return the correct day names based on default locale', () => { + const daysNames = calendarCore.getDaysNames('short') + expect(daysNames).toEqual([ + 'Sun', + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat', + ]) }) - expect(workWeeks.length).toBeGreaterThan(0) - expect(workWeeks[0]?.length).toBe(5) - expect(workWeeks[0]?.[0]?.date.toString()).toBe('2024-06-10[u-ca=gregory]') - expect(workWeeks[0]?.[4]?.date.toString()).toBe('2024-06-14[u-ca=gregory]') + test('should return the correct day names based on custom locale', () => { + const customLocale = 'pl' + calendarCore = new CalendarCore({ ...options, locale: customLocale }) + const customDaysNames = calendarCore.getDaysNames('short') + expect(customDaysNames).toEqual([ + 'pon.', + 'wt.', + 'śr.', + 'czw.', + 'pt.', + 'sob.', + 'niedz.', + ]) + }) }) }) From d54434818b03351ab0b13d10b67c8407818e0ec6 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 3 Jul 2024 23:02:21 +0200 Subject: [PATCH 114/128] chore: add the get-week-info-polyfill --- packages/time/package.json | 1 + packages/time/src/core/calendar.ts | 2 +- .../time/src/core/weekInfoPolyfill/index.ts | 1 - .../src/core/weekInfoPolyfill/weekInfoData.ts | 1832 ----------------- .../core/weekInfoPolyfill/weekInfoPolyfill.ts | 41 - pnpm-lock.yaml | 7 + 6 files changed, 9 insertions(+), 1875 deletions(-) delete mode 100644 packages/time/src/core/weekInfoPolyfill/index.ts delete mode 100644 packages/time/src/core/weekInfoPolyfill/weekInfoData.ts delete mode 100644 packages/time/src/core/weekInfoPolyfill/weekInfoPolyfill.ts diff --git a/packages/time/package.json b/packages/time/package.json index 44f5812..ac995da 100644 --- a/packages/time/package.json +++ b/packages/time/package.json @@ -57,6 +57,7 @@ "src" ], "dependencies": { + "@bart-krakowski/get-week-info-polyfill": "^1.0.2", "@js-temporal/polyfill": "^0.4.4", "@tanstack/store": "^0.4.1" }, diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index cf6553a..242dff0 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -9,7 +9,7 @@ import { getDateDefaults } from '../utils/dateDefaults' import type { GroupDaysByProps } from '../calendar/groupDaysBy' import type { CalendarStore, Day, Event, Resource } from '../calendar/types' -import './weekInfoPolyfill' +import '@bart-krakowski/get-week-info-polyfill' export type * from '../calendar/types' diff --git a/packages/time/src/core/weekInfoPolyfill/index.ts b/packages/time/src/core/weekInfoPolyfill/index.ts deleted file mode 100644 index 92eafb3..0000000 --- a/packages/time/src/core/weekInfoPolyfill/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './weekInfoPolyfill' diff --git a/packages/time/src/core/weekInfoPolyfill/weekInfoData.ts b/packages/time/src/core/weekInfoPolyfill/weekInfoData.ts deleted file mode 100644 index 26c8205..0000000 --- a/packages/time/src/core/weekInfoPolyfill/weekInfoData.ts +++ /dev/null @@ -1,1832 +0,0 @@ -export const weekInfoData: Record = { - af: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ak: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - am: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ar: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hy: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - as: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - asa: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - az: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bm: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - eu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - be: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bem: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bez: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bs: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - my: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ca: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - tzm: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - chr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cgg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - zh: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cs: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - da: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ebu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - en: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - eo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - et: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ee: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fil: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ff: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ka: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - de: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - el: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - guz: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ha: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - haw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - he: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - hu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - is: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ig: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - id: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ga: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - it: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ja: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kea: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kab: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kln: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kam: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - km: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ki: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kok: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ko: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - khq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ses: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lag: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lv: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - lt: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - luo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - luy: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - jmc: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - kde: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ms: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ml: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mt: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gv: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mas: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mer: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - mfe: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - naq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ne: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nd: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nb: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - nyn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - or: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - om: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ps: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - fa: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - pl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - pt: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - pa: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ro: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rm: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rof: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ru: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - rwk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - saq: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sg: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - seh: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sn: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ii: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - si: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sl: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - xog: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - so: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - es: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - sv: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - gsw: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - shi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - dav: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ta: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - te: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - teo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - th: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - bo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ti: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - to: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - tr: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - uk: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - ur: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - uz: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - vi: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - vun: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - cy: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - yo: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - zu: { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'af-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'am-ET': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-AE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-BH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-DZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-EG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-IQ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-JO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-KW': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-LB': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-LY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-MA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'arn-CL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-OM': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-QA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-SA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-SD': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-SY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-TN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ar-YE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'as-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'az-az': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'az-Cyrl-AZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'az-Latn-AZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ba-RU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'be-BY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bg-BG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bn-BD': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bn-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bo-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'br-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bs-Cyrl-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'bs-Latn-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ca-ES': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'co-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'cs-CZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'cy-GB': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'da-DK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-AT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-CH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-DE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-LI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'de-LU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'dsb-DE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'dv-MV': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'el-CY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'el-GR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-029': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-AU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-BZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-cb': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-GB': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-IE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-JM': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-MT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-MY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-NZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-PH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-SG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-TT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-US': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'en-ZW': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-AR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-BO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-CL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-CO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-CR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-DO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-EC': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-ES': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-GT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-HN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-MX': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-NI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-PA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-PE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-PR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-PY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-SV': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-US': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-UY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'es-VE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'et-EE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'eu-ES': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fa-IR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fi-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fil-PH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fo-FO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-BE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-CH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-LU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fr-MC': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'fy-NL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ga-IE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gd-GB': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gd-ie': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gl-ES': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gsw-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'gu-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ha-Latn-NG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'he-IL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hi-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hr-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hr-HR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hsb-DE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hu-HU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'hy-AM': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'id-ID': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ig-NG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ii-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'in-ID': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'is-IS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'it-CH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'it-IT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'iu-Cans-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'iu-Latn-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'iw-IL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ja-JP': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ka-GE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'kk-KZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'kl-GL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'km-KH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'kn-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'kok-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ko-KR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ky-KG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'lb-LU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'lo-LA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'lt-LT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'lv-LV': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mi-NZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mk-MK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ml-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mn-MN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mn-Mong-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'moh-CA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mr-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ms-BN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ms-MY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'mt-MT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nb-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ne-NP': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nl-BE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nl-NL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nn-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'no-no': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'nso-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'oc-FR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'or-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'pa-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'pl-PL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'prs-AF': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ps-AF': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'pt-BR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'pt-PT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'qut-GT': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'quz-BO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'quz-EC': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'quz-PE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'rm-CH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ro-mo': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ro-RO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ru-mo': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ru-RU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'rw-RW': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sah-RU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sa-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'se-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'se-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'se-SE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'si-LK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sk-SK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sl-SI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sma-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sma-SE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'smj-NO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'smj-SE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'smn-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sms-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sq-AL': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-CS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Cyrl-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Cyrl-CS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Cyrl-ME': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Cyrl-RS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Latn-BA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Latn-CS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Latn-ME': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-Latn-RS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-ME': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-RS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sr-sp': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sv-FI': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sv-SE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'sw-KE': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'syr-SY': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ta-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'te-IN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tg-Cyrl-TJ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'th-TH': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tk-TM': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tlh-QS': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tn-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tr-TR': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tt-RU': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'tzm-Latn-DZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ug-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'uk-UA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'ur-PK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'uz-Cyrl-UZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'uz-Latn-UZ': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'uz-uz': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'vi-VN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'wo-SN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'xh-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'yo-NG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-CN': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-HK': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-MO': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-SG': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zh-TW': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, - 'zu-ZA': { - firstDay: 1, - weekend: [6, 7], - minimalDays: 4, - }, -} \ No newline at end of file diff --git a/packages/time/src/core/weekInfoPolyfill/weekInfoPolyfill.ts b/packages/time/src/core/weekInfoPolyfill/weekInfoPolyfill.ts deleted file mode 100644 index a722d0a..0000000 --- a/packages/time/src/core/weekInfoPolyfill/weekInfoPolyfill.ts +++ /dev/null @@ -1,41 +0,0 @@ -interface WeekInfo { - firstDay: number - weekend: number[] - minimalDays: number -} - -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Intl { - interface Locale { - getWeekInfo: () => WeekInfo - weekInfo?: WeekInfo - } -} - -;(function () { - // Chrome & Safari - if ('weekInfo' in Intl.Locale.prototype && typeof Intl.Locale.prototype.getWeekInfo !== 'function') { - Intl.Locale.prototype.getWeekInfo = function () { - return this.weekInfo - } - } - // Firefox - if (typeof Intl.Locale.prototype.getWeekInfo !== 'function') { - import('./weekInfoData').then(({ weekInfoData }) => { - Intl.Locale.prototype.getWeekInfo = function () { - const locale = this.toString().toLowerCase() - - const match = - weekInfoData[locale] || - weekInfoData[locale.split('-')[0]] || - weekInfoData['default'] - - return { - firstDay: match?.firstDay, - weekend: match?.weekend, - minimalDays: match?.minimalDays, - } - } - }) - } -})() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 626d76f..92cea18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,6 +194,9 @@ importers: packages/time: dependencies: + '@bart-krakowski/get-week-info-polyfill': + specifier: ^1.0.2 + version: 1.0.2 '@js-temporal/polyfill': specifier: ^0.4.4 version: 0.4.4 @@ -1840,6 +1843,10 @@ packages: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 + /@bart-krakowski/get-week-info-polyfill@1.0.2: + resolution: {integrity: sha512-wqDBdZBzs3H6PmeXtasxA52Ls980AS2g31TCy6xEUyuURXVuVAWRrPh4d3ptyvbwkQ3Nn7xFPj1Fjppf6sOKhA==} + dev: false + /@commitlint/parse@18.6.1: resolution: {integrity: sha512-eS/3GREtvVJqGZrwAGRwR9Gdno3YcZ6Xvuaa+vUF8j++wsmxrA2En3n0ccfVO2qVOLJC41ni7jSZhQiJpMPGOQ==} engines: {node: '>=v18'} From d0904831a8f65d6244109746bf7df88701948434 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 3 Jul 2024 23:17:27 +0200 Subject: [PATCH 115/128] feat: getWeekInfo polyfill --- packages/time/package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/time/package.json b/packages/time/package.json index ac995da..d8c01d8 100644 --- a/packages/time/package.json +++ b/packages/time/package.json @@ -57,7 +57,7 @@ "src" ], "dependencies": { - "@bart-krakowski/get-week-info-polyfill": "^1.0.2", + "@bart-krakowski/get-week-info-polyfill": "^1.0.3", "@js-temporal/polyfill": "^0.4.4", "@tanstack/store": "^0.4.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92cea18..b175bf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,8 +195,8 @@ importers: packages/time: dependencies: '@bart-krakowski/get-week-info-polyfill': - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^1.0.3 + version: 1.0.3 '@js-temporal/polyfill': specifier: ^0.4.4 version: 0.4.4 @@ -1843,8 +1843,8 @@ packages: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - /@bart-krakowski/get-week-info-polyfill@1.0.2: - resolution: {integrity: sha512-wqDBdZBzs3H6PmeXtasxA52Ls980AS2g31TCy6xEUyuURXVuVAWRrPh4d3ptyvbwkQ3Nn7xFPj1Fjppf6sOKhA==} + /@bart-krakowski/get-week-info-polyfill@1.0.3: + resolution: {integrity: sha512-Rg3nnpR1WuVNtL0A+OxdQpzHfMRNQOezJcYPZQ1XR8VdiefZZw8ea0MMjhfu13pELwsMRxZmsbmGYX5rJTN5WQ==} dev: false /@commitlint/parse@18.6.1: From 178aa347b7d94c2a4f289821499d235abe9e37b7 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 3 Jul 2024 23:46:33 +0200 Subject: [PATCH 116/128] refactor: getEventProps --- packages/time/src/calendar/getEventProps.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/time/src/calendar/getEventProps.ts b/packages/time/src/calendar/getEventProps.ts index 7e3c0d1..38aa2e9 100644 --- a/packages/time/src/calendar/getEventProps.ts +++ b/packages/time/src/calendar/getEventProps.ts @@ -13,22 +13,6 @@ export const getEventProps = ( const eventEndDate = Temporal.ZonedDateTime.from(event.end); const isSplitEvent = Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0; - let eventHeightInMinutes; - - if (isSplitEvent) { - const isStartPart = eventStartDate.hour !== 0 || eventStartDate.minute !== 0; - if (isStartPart) { - const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute; - eventHeightInMinutes = 24 * 60 - eventTimeInMinutes; - } else { - eventHeightInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; - } - } else { - const eventTimeInMinutes = eventStartDate.hour * 60 + eventStartDate.minute; - const endTimeInMinutes = eventEndDate.hour * 60 + eventEndDate.minute; - eventHeightInMinutes = endTimeInMinutes - eventTimeInMinutes; - } - const overlappingEvents = [...eventMap.values()].flat().filter((e) => { const eStartDate = Temporal.ZonedDateTime.from(e.start); const eEndDate = Temporal.ZonedDateTime.from(e.end); @@ -47,7 +31,6 @@ export const getEventProps = ( if (state.viewMode.unit === 'week' || state.viewMode.unit === 'day') { return { - eventHeightInMinutes, isSplitEvent, overlappingEvents, }; From 79fdfddf6a822ab2aa3c381ca220b2ae5eade2c9 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 3 Jul 2024 23:51:07 +0200 Subject: [PATCH 117/128] refactor: getEventProps --- packages/time/src/core/calendar.ts | 1 - packages/time/src/tests/calendar-core.test.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 242dff0..1925fc4 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -68,7 +68,6 @@ interface CalendarActions< changeViewMode: (newViewMode: CalendarStore['viewMode']) => void /** Retrieves styling properties for a specific event, identified by ID. */ getEventProps: (id: Event['id']) => { - eventHeightInMinutes: number isSplitEvent: boolean overlappingEvents: TEvent[] } | null diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 4f6a81e..a444cc7 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -112,7 +112,6 @@ describe('CalendarCore', () => { test('should return the correct props for an event', () => { const eventProps = calendarCore.getEventProps('1') expect(eventProps).toEqual({ - eventHeightInMinutes: 60, isSplitEvent: false, overlappingEvents: [], }) @@ -123,13 +122,11 @@ describe('CalendarCore', () => { const event2Props = calendarCore.getEventProps('3') expect(event1Props).toEqual({ - eventHeightInMinutes: 60, isSplitEvent: false, overlappingEvents: [options.events![2]], }) expect(event2Props).toEqual({ - eventHeightInMinutes: 120, isSplitEvent: false, overlappingEvents: [options.events![1]], }) From 20fb06d40037d8f3ea95f771f98f67f0963131f2 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 4 Jul 2024 00:06:51 +0200 Subject: [PATCH 118/128] refactor: Update generateDateRange to accept string inputs --- packages/time/src/calendar/generateDateRange.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/time/src/calendar/generateDateRange.ts b/packages/time/src/calendar/generateDateRange.ts index ec73960..7aeaaca 100644 --- a/packages/time/src/calendar/generateDateRange.ts +++ b/packages/time/src/calendar/generateDateRange.ts @@ -1,12 +1,18 @@ import { Temporal } from '@js-temporal/polyfill' +import { validateDate } from '../utils/validateDate' export const generateDateRange = ( - start: Temporal.PlainDate, - end: Temporal.PlainDate, + start: string, + end: string, ): Temporal.PlainDate[] => { + validateDate({ date: start }) + validateDate({ date: end }) + + const startDate = Temporal.PlainDate.from(start) + const endDate = Temporal.PlainDate.from(end) const dates: Temporal.PlainDate[] = [] - let current = start - while (Temporal.PlainDate.compare(current, end) <= 0) { + let current = startDate + while (Temporal.PlainDate.compare(current, endDate) <= 0) { dates.push(current) current = current.add({ days: 1 }) } From 8ae1bcdf4c9f3507dfb16fdc992ada9d3bc279a6 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 4 Jul 2024 10:43:02 +0200 Subject: [PATCH 119/128] chore: upgrade polyfill --- packages/time/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/time/package.json b/packages/time/package.json index d8c01d8..abf3fa5 100644 --- a/packages/time/package.json +++ b/packages/time/package.json @@ -57,7 +57,7 @@ "src" ], "dependencies": { - "@bart-krakowski/get-week-info-polyfill": "^1.0.3", + "@bart-krakowski/get-week-info-polyfill": "^1.0.4", "@js-temporal/polyfill": "^0.4.4", "@tanstack/store": "^0.4.1" }, From 0e974ac6466377647dbfb4edb0021031f7e6f552 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 4 Jul 2024 13:16:22 +0200 Subject: [PATCH 120/128] fix: getEventProps --- packages/time/src/calendar/getEventProps.ts | 50 +++++++++++-------- packages/time/src/tests/calendar-core.test.ts | 2 + 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/time/src/calendar/getEventProps.ts b/packages/time/src/calendar/getEventProps.ts index 38aa2e9..0aba95b 100644 --- a/packages/time/src/calendar/getEventProps.ts +++ b/packages/time/src/calendar/getEventProps.ts @@ -1,40 +1,46 @@ -import { Temporal } from "@js-temporal/polyfill"; -import type { CalendarStore, Event } from "./types"; +import { Temporal } from '@js-temporal/polyfill' +import type { CalendarStore, Event } from './types' export const getEventProps = ( eventMap: Map, id: Event['id'], state: CalendarStore, ) => { - const event = [...eventMap.values()].flat().find((currEvent) => currEvent.id === id); - if (!event) return null; + const event = [...eventMap.values()] + .flat() + .find((currEvent) => currEvent.id === id) + if (!event) return null - const eventStartDate = Temporal.ZonedDateTime.from(event.start); - const eventEndDate = Temporal.ZonedDateTime.from(event.end); - const isSplitEvent = Temporal.PlainDate.compare(eventStartDate.toPlainDate(), eventEndDate.toPlainDate()) !== 0; + const eventStartDate = Temporal.ZonedDateTime.from(event.start) + const eventEndDate = Temporal.ZonedDateTime.from(event.end) + const isSplitEvent = + Temporal.PlainDate.compare( + eventStartDate.toPlainDate(), + eventEndDate.toPlainDate(), + ) !== 0 const overlappingEvents = [...eventMap.values()].flat().filter((e) => { - const eStartDate = Temporal.ZonedDateTime.from(e.start); - const eEndDate = Temporal.ZonedDateTime.from(e.end); + const eStartDate = Temporal.ZonedDateTime.from(e.start) + const eEndDate = Temporal.ZonedDateTime.from(e.end) return ( - (e.id !== id && - Temporal.ZonedDateTime.compare(eventStartDate, eStartDate) >= 0 && + e.id !== id && + ((Temporal.ZonedDateTime.compare(eventStartDate, eStartDate) >= 0 && Temporal.ZonedDateTime.compare(eventStartDate, eEndDate) <= 0) || - (Temporal.ZonedDateTime.compare(eventEndDate, eStartDate) >= 0 && - Temporal.ZonedDateTime.compare(eventEndDate, eEndDate) <= 0) || - (Temporal.ZonedDateTime.compare(eStartDate, eventStartDate) >= 0 && - Temporal.ZonedDateTime.compare(eStartDate, eventEndDate) <= 0) || - (Temporal.ZonedDateTime.compare(eEndDate, eventStartDate) >= 0 && - Temporal.ZonedDateTime.compare(eEndDate, eventEndDate) <= 0) - ); - }); + (Temporal.ZonedDateTime.compare(eventEndDate, eStartDate) >= 0 && + Temporal.ZonedDateTime.compare(eventEndDate, eEndDate) <= 0) || + (Temporal.ZonedDateTime.compare(eStartDate, eventStartDate) >= 0 && + Temporal.ZonedDateTime.compare(eStartDate, eventEndDate) <= 0) || + (Temporal.ZonedDateTime.compare(eEndDate, eventStartDate) >= 0 && + Temporal.ZonedDateTime.compare(eEndDate, eventEndDate) <= 0)) + ) + }) if (state.viewMode.unit === 'week' || state.viewMode.unit === 'day') { return { isSplitEvent, overlappingEvents, - }; + } } - return null; -}; \ No newline at end of file + return null +} diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index a444cc7..824dc4b 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -110,6 +110,7 @@ describe('CalendarCore', () => { }) test('should return the correct props for an event', () => { + calendarCore.changeViewMode({ value: 1, unit: 'day' }) const eventProps = calendarCore.getEventProps('1') expect(eventProps).toEqual({ isSplitEvent: false, @@ -118,6 +119,7 @@ describe('CalendarCore', () => { }) test('should return the correct props for overlapping events', () => { + calendarCore.changeViewMode({ value: 1, unit: 'day' }) const event1Props = calendarCore.getEventProps('2') const event2Props = calendarCore.getEventProps('3') From 85c6a7708b1a694454a832892904b76d6c41ce47 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 4 Jul 2024 13:22:03 +0200 Subject: [PATCH 121/128] fix: getEventProps --- packages/time/src/core/calendar.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 1925fc4..d9c7d19 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -63,7 +63,7 @@ interface CalendarActions< /** Resets the view to the current period based on today's date. */ goToCurrentPeriod: () => void /** Navigates to a specific date. */ - goToSpecificPeriod: (date: Temporal.PlainDate) => void + goToSpecificPeriod: (date: string) => void /** Changes the current view mode of the calendar. */ changeViewMode: (newViewMode: CalendarStore['viewMode']) => void /** Retrieves styling properties for a specific event, identified by ID. */ @@ -90,7 +90,7 @@ interface CalendarState< /** An array of days, each potentially containing events. */ days: Array> /** The currently active date in the calendar. */ - activeDate: Temporal.PlainDate + activeDate: CalendarStore['activeDate'] } export interface CalendarApi< @@ -189,7 +189,7 @@ export class CalendarCore< } } - const allDays = generateDateRange(start, end) + const allDays = generateDateRange(start.toString(), end.toString()) const startMonthDate = this.store.state.currentPeriod.with({ day: 1 }) const endMonthDate = this.store.state.currentPeriod .add({ @@ -392,11 +392,11 @@ export class CalendarCore< })) } - goToSpecificPeriod(date: Temporal.PlainDate) { + goToSpecificPeriod(date: string) { this.store.setState((prev) => ({ ...prev, - activeDate: date, - currentPeriod: date, + activeDate: Temporal.PlainDate.from(date), + currentPeriod: Temporal.PlainDate.from(date), })) } From 72d43d4b58105cf0e9388ab9a1d94260b9d06220 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 4 Jul 2024 14:01:45 +0200 Subject: [PATCH 122/128] refactor: update date inputs to accept string format --- .../time/src/calendar/splitMultiDayEvents.ts | 10 ++-------- packages/time/src/calendar/types.ts | 4 ++-- packages/time/src/core/calendar.ts | 16 +++++++--------- packages/time/src/tests/calendar-core.test.ts | 14 +++++++------- 4 files changed, 18 insertions(+), 26 deletions(-) diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts index 8aa58e6..dd99059 100644 --- a/packages/time/src/calendar/splitMultiDayEvents.ts +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -9,14 +9,8 @@ export const splitMultiDayEvents = < event: TEvent, timeZone: Temporal.TimeZoneLike, ): TEvent[] => { - const startDate = - event.start instanceof Temporal.PlainDateTime - ? event.start.toZonedDateTime(timeZone) - : event.start - const endDate = - event.end instanceof Temporal.PlainDateTime - ? event.end.toZonedDateTime(timeZone) - : event.end + const startDate = Temporal.PlainDateTime.from(event.start).toZonedDateTime(timeZone) + const endDate = Temporal.PlainDateTime.from(event.end).toZonedDateTime(timeZone) const events: TEvent[] = [] let currentDay = startDate diff --git a/packages/time/src/calendar/types.ts b/packages/time/src/calendar/types.ts index 30c7b79..5d7b888 100644 --- a/packages/time/src/calendar/types.ts +++ b/packages/time/src/calendar/types.ts @@ -4,8 +4,8 @@ export type Resource = string | null export interface Event { id: string - start: Temporal.PlainDateTime | Temporal.ZonedDateTime - end: Temporal.PlainDateTime | Temporal.ZonedDateTime + start: string + end: string title: string resources?: TResource[] } diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index d9c7d19..102ed6d 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -157,7 +157,7 @@ export class CalendarCore< }) : this.store.state.currentPeriod - let end + let end: Temporal.PlainDate switch (this.store.state.viewMode.unit) { case 'month': { const lastDayOfMonth = this.getFirstDayOfMonth() @@ -211,14 +211,12 @@ export class CalendarCore< private getEventMap() { const map = new Map() this.options.events?.forEach((event) => { - const eventStartDate = - event.start instanceof Temporal.PlainDateTime - ? event.start.toZonedDateTime(this.options.timeZone) - : event.start - const eventEndDate = - event.end instanceof Temporal.PlainDateTime - ? event.end.toZonedDateTime(this.options.timeZone) - : event.end + const eventStartDate = Temporal.PlainDateTime.from( + event.start, + ).toZonedDateTime(this.options.timeZone) + const eventEndDate = Temporal.PlainDateTime.from( + event.end, + ).toZonedDateTime(this.options.timeZone) if (Temporal.ZonedDateTime.compare(eventStartDate, eventEndDate) !== 0) { const splitEvents = splitMultiDayEvents( event, diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 824dc4b..9def0f2 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -41,20 +41,20 @@ describe('CalendarCore', () => { events: [ { id: '1', - start: Temporal.PlainDateTime.from('2024-06-10T09:00'), - end: Temporal.PlainDateTime.from('2024-06-10T10:00'), + start: '2024-06-10T09:00', + end: '2024-06-10T10:00', title: 'Event 1', }, { id: '2', - start: Temporal.PlainDateTime.from('2024-06-12T11:00'), - end: Temporal.PlainDateTime.from('2024-06-12T12:00'), + start: '2024-06-12T11:00', + end: '2024-06-12T12:00', title: 'Event 2', }, { id: '3', - start: Temporal.PlainDateTime.from('2024-06-12T11:00'), - end: Temporal.PlainDateTime.from('2024-06-12T13:00'), + start: '2024-06-12T11:00', + end: '2024-06-12T13:00', title: 'Event 3', }, ], @@ -177,7 +177,7 @@ describe('CalendarCore', () => { }) test('should go to specific period correctly', () => { - const specificDate = Temporal.PlainDate.from('2024-07-01') + const specificDate = '2024-07-01' calendarCore.goToSpecificPeriod(specificDate) expect(calendarCore.store.state.currentPeriod).toEqual(specificDate) expect(calendarCore.store.state.activeDate).toEqual(specificDate) From cbc21864c749831a22de27b116840accb36a60a1 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 4 Jul 2024 14:27:26 +0200 Subject: [PATCH 123/128] refactor: convert activeDate, currentPeriod to string in useCalendar --- packages/react-time/src/useCalendar/useCalendar.ts | 4 +++- packages/time/src/core/calendar.ts | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/react-time/src/useCalendar/useCalendar.ts b/packages/react-time/src/useCalendar/useCalendar.ts index 8286036..23441b0 100644 --- a/packages/react-time/src/useCalendar/useCalendar.ts +++ b/packages/react-time/src/useCalendar/useCalendar.ts @@ -47,7 +47,9 @@ export const useCalendar = ((props) => calendarCore.getDaysNames(props), [calendarCore]) return { - ...state, + activeDate: state.activeDate.toString(), + currentPeriod: state.currentPeriod.toString(), + viewMode: state.viewMode, days: calendarCore.getDaysWithEvents(), getDaysNames, goToPreviousPeriod, diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 102ed6d..4c17cc3 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -93,11 +93,15 @@ interface CalendarState< activeDate: CalendarStore['activeDate'] } +type ConvertTemporalToString = { + [K in keyof T]: T[K] extends Temporal.PlainDate ? string : T[K] +} + export interface CalendarApi< TResource extends Resource, TEvent extends Event, > extends CalendarActions, - CalendarState {} + ConvertTemporalToString> {} /** * Core functionality for a calendar system, managing the state and operations of the calendar, From 1f6048ecee2b373a08a41f59d7d4ccd03422c095 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 18 Jul 2024 23:16:53 +0200 Subject: [PATCH 124/128] refactor: startOf and endOf helpers --- .../8560fb4b2dbac6ca-turbo.log.2024-07-18 | 0 .../time/src/calendar/splitMultiDayEvents.ts | 8 +- packages/time/src/tests/endOf.test.ts | 47 ++++++++++++ packages/time/src/tests/startOf.test.ts | 49 ++++++++++++ packages/time/src/utils/endOf.ts | 72 +++++++++++++++--- packages/time/src/utils/startOf.ts | 74 ++++++++++++++++--- 6 files changed, 224 insertions(+), 26 deletions(-) create mode 100644 .turbo/daemon/8560fb4b2dbac6ca-turbo.log.2024-07-18 create mode 100644 packages/time/src/tests/endOf.test.ts create mode 100644 packages/time/src/tests/startOf.test.ts diff --git a/.turbo/daemon/8560fb4b2dbac6ca-turbo.log.2024-07-18 b/.turbo/daemon/8560fb4b2dbac6ca-turbo.log.2024-07-18 new file mode 100644 index 0000000..e69de29 diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts index dd99059..e59e649 100644 --- a/packages/time/src/calendar/splitMultiDayEvents.ts +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -18,15 +18,15 @@ export const splitMultiDayEvents = < const eventStart = Temporal.PlainDateTime.compare(currentDay, startDate) === 0 ? startDate - : startOf(currentDay) + : startOf(currentDay, 'day') const eventEnd = - Temporal.PlainDateTime.compare(endDate, endOf(currentDay)) <= 0 + Temporal.PlainDateTime.compare(endDate, endOf(currentDay, 'day')) < 0 ? endDate - : endOf(currentDay) + : endOf(currentDay, 'day') events.push({ ...event, start: eventStart, end: eventEnd }) - currentDay = startOf(currentDay).add({ days: 1 }) + currentDay = startOf(currentDay, 'day').add({ days: 1 }) } return events diff --git a/packages/time/src/tests/endOf.test.ts b/packages/time/src/tests/endOf.test.ts new file mode 100644 index 0000000..a29b4d6 --- /dev/null +++ b/packages/time/src/tests/endOf.test.ts @@ -0,0 +1,47 @@ +import { Temporal } from '@js-temporal/polyfill' +import { describe, expect, test } from 'vitest' +import { endOf } from '../utils' + +describe('endOf', () => { + test('should get the end of the given day', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = endOf(date, 'day') + const expected = Temporal.ZonedDateTime.from('2023-07-16T23:59:59.999+01:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the end of the given week', () => { + const date = Temporal.ZonedDateTime.from('2023-07-13T12:34:56.789+01:00[Europe/London]') + const result = endOf(date, 'week') + const expected = Temporal.ZonedDateTime.from('2023-07-16T23:59:59.999+01:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the end of the given month', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = endOf(date, 'month') + const expected = Temporal.ZonedDateTime.from('2023-07-31T23:59:59.999+01:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the end of the given year', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = endOf(date, 'year') + const expected = Temporal.ZonedDateTime.from('2023-12-31T23:59:59.999+00:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the end of the given work week', () => { + const date = Temporal.ZonedDateTime.from('2023-07-10T12:34:56.789+01:00[Europe/London]') + const result = endOf(date, 'workWeek') + const expected = Temporal.ZonedDateTime.from('2023-07-14T23:59:59.999+01:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the end of the given decade', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = endOf(date, 'decade') + const expected = Temporal.ZonedDateTime.from('2029-12-31T23:59:59.999+00:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) +}) diff --git a/packages/time/src/tests/startOf.test.ts b/packages/time/src/tests/startOf.test.ts new file mode 100644 index 0000000..ca26f90 --- /dev/null +++ b/packages/time/src/tests/startOf.test.ts @@ -0,0 +1,49 @@ + +import { Temporal } from '@js-temporal/polyfill' +import { describe, expect, test } from 'vitest' +import { startOf } from '../utils' + +describe('startOf', () => { + test('should get the start of the given day', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = startOf(date, 'day') + const expected = Temporal.ZonedDateTime.from('2023-07-16T00:00:00.000+01:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the start of the given week', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = startOf(date, 'week') + const expected = Temporal.ZonedDateTime.from('2023-07-10T00:00:00.000+01:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the start of the given month', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = startOf(date, 'month') + const expected = Temporal.ZonedDateTime.from('2023-07-01T00:00:00.000+01:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the start of the given year', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = startOf(date, 'year') + console.log('result', result.toString()) + const expected = Temporal.ZonedDateTime.from('2023-01-01T00:00:00.000+00:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the start of the given work week', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = startOf(date, 'workWeek') + const expected = Temporal.ZonedDateTime.from('2023-07-10T00:00:00.000+01:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) + + test('should get the start of the given decade', () => { + const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') + const result = startOf(date, 'decade') + const expected = Temporal.ZonedDateTime.from('2020-01-01T00:00:00.000+00:00[Europe/London]') + expect(result.equals(expected)).toBe(true) + }) +}) diff --git a/packages/time/src/utils/endOf.ts b/packages/time/src/utils/endOf.ts index 4742df9..70bf48c 100644 --- a/packages/time/src/utils/endOf.ts +++ b/packages/time/src/utils/endOf.ts @@ -1,15 +1,67 @@ import type { Temporal } from '@js-temporal/polyfill' /** - * Helper function to get the end of a given day. - * @param date - The date for which the end of the day is needed. - * @returns The end of the given day. + * Helper function to get the end of a given temporal unit. + * @param date {Temporal.ZonedDateTime} - The date for which the end of the unit is needed. + * @param unit {Unit} - The unit for which to find the end ('day', 'week', 'month', 'year', 'workWeek', 'decade'). + * @returns {Temporal.ZonedDateTime} The end of the given unit. */ -export const endOf = (date: Temporal.ZonedDateTime): Temporal.ZonedDateTime => { - return date.with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }) +export const endOf = (date: Temporal.ZonedDateTime, unit: 'day' | 'week' | 'month' | 'year' | 'workWeek' | 'decade'): Temporal.ZonedDateTime => { + switch (unit) { + case 'day': + return date.with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }); + case 'week': { + const endOfWeek = date.add({ days: 7 - date.dayOfWeek }).with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }); + return endOfWeek; + } + case 'month': { + const lastDayOfMonth = date.with({ day: 1 }).add({ months: 1 }).subtract({ days: 1 }); + return lastDayOfMonth.with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }); + } + case 'year': { + const lastDayOfYear = date.with({ month: 12, day: 31 }); + return lastDayOfYear.with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }); + } + case 'workWeek': { + const endOfWorkWeek = date.add({ days: 5 - date.dayOfWeek }).with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }); + return endOfWorkWeek; + } + case 'decade': { + const lastYearOfDecade = date.with({ year: date.year - (date.year % 10) + 9 }); + const lastDayOfDecade = lastYearOfDecade.with({ month: 12, day: 31 }); + return lastDayOfDecade.with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }); + } + default: + throw new Error(`Unsupported unit: ${unit}`); + } } diff --git a/packages/time/src/utils/startOf.ts b/packages/time/src/utils/startOf.ts index d5723e3..3cd6c87 100644 --- a/packages/time/src/utils/startOf.ts +++ b/packages/time/src/utils/startOf.ts @@ -1,17 +1,67 @@ import type { Temporal } from '@js-temporal/polyfill' /** - * Helper function to get the start of a given day. - * @param date - The date for which the start of the day is needed. - * @returns The start of the given day. + * Helper function to get the start of a given temporal unit. + * @param date {Temporal.ZonedDateTime} - The date for which the start of the unit is needed. + * @param unit {Unit} - The unit for which to find the start ('day', 'week', 'month', 'year', 'workWeek', 'decade'). + * @returns {Temporal.ZonedDateTime} The start of the given unit. */ -export const startOf = ( - date: Temporal.ZonedDateTime, -): Temporal.ZonedDateTime => { - return date.with({ - hour: 0, - minute: 0, - second: 0, - millisecond: 0, - }) +export const startOf = (date: Temporal.ZonedDateTime, unit: 'day' | 'week' | 'month' | 'year' | 'workWeek' | 'decade'): Temporal.ZonedDateTime => { + switch (unit) { + case 'day': + return date.with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }); + case 'week': { + const startOfWeek = date.subtract({ days: date.dayOfWeek - 1 }).with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }); + return startOfWeek; + } + case 'month': { + const startOfMonth = date.with({ day: 1 }).with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }); + return startOfMonth; + } + case 'year': { + const startOfYear = date.with({ month: 1, day: 1 }).with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }); + return startOfYear; + } + case 'workWeek': { + const dayOfWeek = date.dayOfWeek; + const startOfWorkWeek = dayOfWeek === 1 ? date : date.subtract({ days: dayOfWeek - 1 }); + return startOfWorkWeek.with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }); + } + case 'decade': { + const startOfDecade = date.with({ year: date.year - (date.year % 10), month: 1, day: 1 }).with({ + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + }); + return startOfDecade; + } + default: + throw new Error(`Unsupported unit: ${unit}`); + } } From 291cfb15ba5ed5b5effe0531f3a6a88bcba961e7 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 18 Jul 2024 23:42:02 +0200 Subject: [PATCH 125/128] test: calendar core --- packages/time/src/tests/calendar-core.test.ts | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 9def0f2..7a1c132 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -125,12 +125,34 @@ describe('CalendarCore', () => { expect(event1Props).toEqual({ isSplitEvent: false, - overlappingEvents: [options.events![2]], + overlappingEvents: [ + { + ...options.events![2], + start: + Temporal.PlainDateTime.from('2024-06-12T11:00').toZonedDateTime( + mockTimeZone, + ), + end: Temporal.PlainDateTime.from( + '2024-06-12T13:00', + ).toZonedDateTime(mockTimeZone), + }, + ], }) expect(event2Props).toEqual({ isSplitEvent: false, - overlappingEvents: [options.events![1]], + overlappingEvents: [ + { + ...options.events![1], + start: + Temporal.PlainDateTime.from('2024-06-12T11:00').toZonedDateTime( + mockTimeZone, + ), + end: Temporal.PlainDateTime.from( + '2024-06-12T12:00', + ).toZonedDateTime(mockTimeZone), + }, + ], }) }) }) @@ -179,8 +201,12 @@ describe('CalendarCore', () => { test('should go to specific period correctly', () => { const specificDate = '2024-07-01' calendarCore.goToSpecificPeriod(specificDate) - expect(calendarCore.store.state.currentPeriod).toEqual(specificDate) - expect(calendarCore.store.state.activeDate).toEqual(specificDate) + expect(calendarCore.store.state.currentPeriod.toString()).toEqual( + specificDate, + ) + expect(calendarCore.store.state.activeDate.toString()).toEqual( + specificDate, + ) }) test('should go to previous workWeek correctly', () => { @@ -241,21 +267,16 @@ describe('CalendarCore', () => { }) test('should group days by workWeek correctly', () => { - calendarCore.changeViewMode({ value: 1, unit: 'workWeek' }) const daysWithEvents = calendarCore.getDaysWithEvents() const workWeeks = calendarCore.groupDaysBy({ days: daysWithEvents, unit: 'workWeek', }) - expect(workWeeks.length).toBeGreaterThan(0) + expect(workWeeks.length).toBe(9) expect(workWeeks[0]?.length).toBe(5) - expect(workWeeks[0]?.[0]?.date.toString()).toBe( - '2024-06-10[u-ca=gregory]', - ) - expect(workWeeks[0]?.[4]?.date.toString()).toBe( - '2024-06-14[u-ca=gregory]', - ) + expect(workWeeks[0]?.[0]?.date.toString()).toBe('2024-05-27') + expect(workWeeks[0]?.[4]?.date.toString()).toBe('2024-05-31') }) test('should group days by workWeek correctly with custom locale', () => { From 3aa0488479e99b4e3781e788d306f770d8f8bd25 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sat, 20 Jul 2024 23:14:45 +0200 Subject: [PATCH 126/128] refactor: update useCalendar to accept string inputs for activeDate and currentPeriod --- packages/react-time/src/tests/useCalendar.test.tsx | 11 ++++++----- packages/time/package.json | 2 +- packages/time/src/core/calendar.ts | 3 ++- packages/time/src/global.d.ts | 1 + pnpm-lock.yaml | 8 ++++---- 5 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 packages/time/src/global.d.ts diff --git a/packages/react-time/src/tests/useCalendar.test.tsx b/packages/react-time/src/tests/useCalendar.test.tsx index d34af12..006e5d4 100644 --- a/packages/react-time/src/tests/useCalendar.test.tsx +++ b/packages/react-time/src/tests/useCalendar.test.tsx @@ -23,14 +23,14 @@ describe('useCalendar', () => { const events: Event[] = [ { id: '1', - start: Temporal.PlainDateTime.from('2024-06-01T10:00:00'), - end: Temporal.PlainDateTime.from('2024-06-01T12:00:00'), + start: '2024-06-01T10:00:00', + end: '2024-06-01T12:00:00', title: 'Event 1', }, { id: '2', - start: Temporal.PlainDateTime.from('2024-06-02T14:00:00'), - end: Temporal.PlainDateTime.from('2024-06-02T16:00:00'), + start: '2024-06-02T14:00:00', + end: '2024-06-02T16:00:00', title: 'Event 2', }, ]; @@ -52,6 +52,7 @@ describe('useCalendar', () => { subscribe: vi.fn(), state: { currentPeriod: mockDate, + activeDate: mockDate, viewMode: { value: 1, unit: 'month' }, }, }; @@ -127,7 +128,7 @@ describe('useCalendar', () => { useCalendar({ events, viewMode: { value: 1, unit: 'month' } }), ); - const specificDate = Temporal.PlainDate.from('2024-06-01'); + const specificDate = '2024-06-01'; act(() => { result.current.goToSpecificPeriod(specificDate); }); diff --git a/packages/time/package.json b/packages/time/package.json index abf3fa5..3edf63a 100644 --- a/packages/time/package.json +++ b/packages/time/package.json @@ -57,7 +57,7 @@ "src" ], "dependencies": { - "@bart-krakowski/get-week-info-polyfill": "^1.0.4", + "@bart-krakowski/get-week-info-polyfill": "^1.0.5", "@js-temporal/polyfill": "^0.4.4", "@tanstack/store": "^0.4.1" }, diff --git a/packages/time/src/core/calendar.ts b/packages/time/src/core/calendar.ts index 4c17cc3..43aec57 100644 --- a/packages/time/src/core/calendar.ts +++ b/packages/time/src/core/calendar.ts @@ -75,7 +75,7 @@ interface CalendarActions< getDaysNames: (weekday?: 'long' | 'short') => string[] /** Groups days by a specified unit. */ groupDaysBy: ( - props: Omit, 'weekStartsOn'>, + props: Omit, 'weekStartsOn' | 'locale'>, ) => (Day | null)[][] } @@ -134,6 +134,7 @@ export class CalendarCore< }) } + private getFirstDayOfMonth() { return getFirstDayOfMonth( this.store.state.currentPeriod diff --git a/packages/time/src/global.d.ts b/packages/time/src/global.d.ts new file mode 100644 index 0000000..fb48dac --- /dev/null +++ b/packages/time/src/global.d.ts @@ -0,0 +1 @@ +/// diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b175bf9..e0c2ad8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,8 +195,8 @@ importers: packages/time: dependencies: '@bart-krakowski/get-week-info-polyfill': - specifier: ^1.0.3 - version: 1.0.3 + specifier: ^1.0.5 + version: 1.0.5 '@js-temporal/polyfill': specifier: ^0.4.4 version: 0.4.4 @@ -1843,8 +1843,8 @@ packages: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - /@bart-krakowski/get-week-info-polyfill@1.0.3: - resolution: {integrity: sha512-Rg3nnpR1WuVNtL0A+OxdQpzHfMRNQOezJcYPZQ1XR8VdiefZZw8ea0MMjhfu13pELwsMRxZmsbmGYX5rJTN5WQ==} + /@bart-krakowski/get-week-info-polyfill@1.0.5: + resolution: {integrity: sha512-TE6yg50I2W0/EJ43OAD76TNleubb/P3w8DcxsGlUZIJNODtBjyqXyxyaj9vwiftb288TBl7Sfst2alNHSp4PCQ==} dev: false /@commitlint/parse@18.6.1: From 8850d2ac13eb4d77bc701ab3b39e78fd5592e0d4 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sat, 20 Jul 2024 23:32:58 +0200 Subject: [PATCH 127/128] refactor: Update get-week-info-polyfill dependency to version 1.0.6 --- packages/time/package.json | 2 +- packages/time/src/global.d.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 packages/time/src/global.d.ts diff --git a/packages/time/package.json b/packages/time/package.json index 3edf63a..285e7e4 100644 --- a/packages/time/package.json +++ b/packages/time/package.json @@ -57,7 +57,7 @@ "src" ], "dependencies": { - "@bart-krakowski/get-week-info-polyfill": "^1.0.5", + "@bart-krakowski/get-week-info-polyfill": "^1.0.6", "@js-temporal/polyfill": "^0.4.4", "@tanstack/store": "^0.4.1" }, diff --git a/packages/time/src/global.d.ts b/packages/time/src/global.d.ts deleted file mode 100644 index fb48dac..0000000 --- a/packages/time/src/global.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// From fe67772294719e28052194e1bb8573062fb9f158 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Sun, 21 Jul 2024 00:06:25 +0200 Subject: [PATCH 128/128] refactor: Update startOf and endOf helpers to accept string inputs --- .../time/src/calendar/splitMultiDayEvents.ts | 18 +-- packages/time/src/tests/calendar-core.test.ts | 8 +- packages/time/src/tests/endOf.test.ts | 74 +++++++++--- packages/time/src/utils/endOf.ts | 109 +++++++++--------- packages/time/src/utils/startOf.ts | 90 ++++++++++----- 5 files changed, 192 insertions(+), 107 deletions(-) diff --git a/packages/time/src/calendar/splitMultiDayEvents.ts b/packages/time/src/calendar/splitMultiDayEvents.ts index e59e649..376460e 100644 --- a/packages/time/src/calendar/splitMultiDayEvents.ts +++ b/packages/time/src/calendar/splitMultiDayEvents.ts @@ -16,18 +16,22 @@ export const splitMultiDayEvents = < let currentDay = startDate while (Temporal.ZonedDateTime.compare(currentDay, endDate) < 0) { const eventStart = - Temporal.PlainDateTime.compare(currentDay, startDate) === 0 + Temporal.ZonedDateTime.compare(currentDay, startDate) === 0 ? startDate - : startOf(currentDay, 'day') + : startOf({ date: currentDay, unit: 'day' }) const eventEnd = - Temporal.PlainDateTime.compare(endDate, endOf(currentDay, 'day')) < 0 + Temporal.ZonedDateTime.compare(endDate, endOf({ date: currentDay, unit: 'day' })) < 0 ? endDate - : endOf(currentDay, 'day') + : endOf({ date: currentDay, unit: 'day' }) - events.push({ ...event, start: eventStart, end: eventEnd }) + events.push({ + ...event, + start: eventStart.toString(), + end: eventEnd.toString(), + }) - currentDay = startOf(currentDay, 'day').add({ days: 1 }) + currentDay = startOf({ date: currentDay, unit: 'day' }).add({ days: 1 }) } return events -} +} \ No newline at end of file diff --git a/packages/time/src/tests/calendar-core.test.ts b/packages/time/src/tests/calendar-core.test.ts index 7a1c132..1d68f72 100644 --- a/packages/time/src/tests/calendar-core.test.ts +++ b/packages/time/src/tests/calendar-core.test.ts @@ -131,10 +131,10 @@ describe('CalendarCore', () => { start: Temporal.PlainDateTime.from('2024-06-12T11:00').toZonedDateTime( mockTimeZone, - ), + ).toString(), end: Temporal.PlainDateTime.from( '2024-06-12T13:00', - ).toZonedDateTime(mockTimeZone), + ).toZonedDateTime(mockTimeZone).toString(), }, ], }) @@ -147,10 +147,10 @@ describe('CalendarCore', () => { start: Temporal.PlainDateTime.from('2024-06-12T11:00').toZonedDateTime( mockTimeZone, - ), + ).toString(), end: Temporal.PlainDateTime.from( '2024-06-12T12:00', - ).toZonedDateTime(mockTimeZone), + ).toZonedDateTime(mockTimeZone).toString(), }, ], }) diff --git a/packages/time/src/tests/endOf.test.ts b/packages/time/src/tests/endOf.test.ts index a29b4d6..f8db833 100644 --- a/packages/time/src/tests/endOf.test.ts +++ b/packages/time/src/tests/endOf.test.ts @@ -5,43 +5,91 @@ import { endOf } from '../utils' describe('endOf', () => { test('should get the end of the given day', () => { const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') - const result = endOf(date, 'day') - const expected = Temporal.ZonedDateTime.from('2023-07-16T23:59:59.999+01:00[Europe/London]') + const result = endOf({ + date, + unit: 'day', + }) + const expected = Temporal.ZonedDateTime.from('2023-07-16T23:59:59.999999999+01:00[Europe/London]') expect(result.equals(expected)).toBe(true) }) test('should get the end of the given week', () => { const date = Temporal.ZonedDateTime.from('2023-07-13T12:34:56.789+01:00[Europe/London]') - const result = endOf(date, 'week') - const expected = Temporal.ZonedDateTime.from('2023-07-16T23:59:59.999+01:00[Europe/London]') + const result = endOf({ + date, + unit: 'week', + }) + const expected = Temporal.ZonedDateTime.from('2023-07-16T23:59:59.999999999+01:00[Europe/London]') expect(result.equals(expected)).toBe(true) }) test('should get the end of the given month', () => { const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') - const result = endOf(date, 'month') - const expected = Temporal.ZonedDateTime.from('2023-07-31T23:59:59.999+01:00[Europe/London]') + const result = endOf({ + date, + unit: 'month', + }) + const expected = Temporal.ZonedDateTime.from('2023-07-31T23:59:59.999999999+01:00[Europe/London]') expect(result.equals(expected)).toBe(true) }) test('should get the end of the given year', () => { const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') - const result = endOf(date, 'year') - const expected = Temporal.ZonedDateTime.from('2023-12-31T23:59:59.999+00:00[Europe/London]') + const result = endOf({ + date, + unit: 'year', + }) + const expected = Temporal.ZonedDateTime.from('2023-12-31T23:59:59.999999999+00:00[Europe/London]') expect(result.equals(expected)).toBe(true) }) test('should get the end of the given work week', () => { const date = Temporal.ZonedDateTime.from('2023-07-10T12:34:56.789+01:00[Europe/London]') - const result = endOf(date, 'workWeek') - const expected = Temporal.ZonedDateTime.from('2023-07-14T23:59:59.999+01:00[Europe/London]') + const result = endOf({ + date, + unit: 'workWeek', + }) + const expected = Temporal.ZonedDateTime.from('2023-07-14T23:59:59.999999999+01:00[Europe/London]') expect(result.equals(expected)).toBe(true) }) test('should get the end of the given decade', () => { const date = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789+01:00[Europe/London]') - const result = endOf(date, 'decade') - const expected = Temporal.ZonedDateTime.from('2029-12-31T23:59:59.999+00:00[Europe/London]') + const result = endOf({ + date, + unit: 'decade', + }) + const expected = Temporal.ZonedDateTime.from('2029-12-31T23:59:59.999999999+00:00[Europe/London]') expect(result.equals(expected)).toBe(true) }) -}) + + test('should handle daylight saving time changes', () => { + // Test for a date just before the DST change + const dateBeforeDST = Temporal.ZonedDateTime.from('2023-10-28T12:00:00+01:00[Europe/London]') + const resultBeforeDST = endOf({ + date: dateBeforeDST, + unit: 'day', + }) + const expectedBeforeDST = Temporal.ZonedDateTime.from('2023-10-28T23:59:59.999999999+01:00[Europe/London]') + expect(resultBeforeDST.equals(expectedBeforeDST)).toBe(true) + + // Test for a date just after the DST change + const dateAfterDST = Temporal.ZonedDateTime.from('2023-10-29T12:00:00+00:00[Europe/London]') + const resultAfterDST = endOf({ + date: dateAfterDST, + unit: 'day', + }) + const expectedAfterDST = Temporal.ZonedDateTime.from('2023-10-29T23:59:59.999999999+00:00[Europe/London]') + expect(resultAfterDST.equals(expectedAfterDST)).toBe(true) + }) + + test('should handle different time zones', () => { + const dateNY = Temporal.ZonedDateTime.from('2023-07-16T12:34:56.789-04:00[America/New_York]') + const resultNY = endOf({ + date: dateNY, + unit: 'day', + }) + const expectedNY = Temporal.ZonedDateTime.from('2023-07-16T23:59:59.999999999-04:00[America/New_York]') + expect(resultNY.equals(expectedNY)).toBe(true) + }) +}) \ No newline at end of file diff --git a/packages/time/src/utils/endOf.ts b/packages/time/src/utils/endOf.ts index 70bf48c..db8e419 100644 --- a/packages/time/src/utils/endOf.ts +++ b/packages/time/src/utils/endOf.ts @@ -1,67 +1,68 @@ import type { Temporal } from '@js-temporal/polyfill' +type ViewUnit = 'month' | 'week' | 'day' | 'workWeek' | 'decade' | 'year' + +interface EndOfParams { + date: Temporal.ZonedDateTime + unit: ViewUnit + viewModeValue?: number + firstDayOfWeek?: number +} + /** * Helper function to get the end of a given temporal unit. - * @param date {Temporal.ZonedDateTime} - The date for which the end of the unit is needed. - * @param unit {Unit} - The unit for which to find the end ('day', 'week', 'month', 'year', 'workWeek', 'decade'). + * @param {EndOfParams} params - The parameters for the endOf function. * @returns {Temporal.ZonedDateTime} The end of the given unit. */ -export const endOf = (date: Temporal.ZonedDateTime, unit: 'day' | 'week' | 'month' | 'year' | 'workWeek' | 'decade'): Temporal.ZonedDateTime => { +export function endOf({ + date, + unit, + viewModeValue = 1, + firstDayOfWeek = 1, +}: EndOfParams): Temporal.ZonedDateTime { + let endDate: Temporal.ZonedDateTime + switch (unit) { case 'day': - return date.with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }); - case 'week': { - const endOfWeek = date.add({ days: 7 - date.dayOfWeek }).with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }); - return endOfWeek; - } - case 'month': { - const lastDayOfMonth = date.with({ day: 1 }).add({ months: 1 }).subtract({ days: 1 }); - return lastDayOfMonth.with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }); - } - case 'year': { - const lastDayOfYear = date.with({ month: 12, day: 31 }); - return lastDayOfYear.with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }); - } - case 'workWeek': { - const endOfWorkWeek = date.add({ days: 5 - date.dayOfWeek }).with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }); - return endOfWorkWeek; - } + endDate = date + break + case 'week': + endDate = date.add({ days: 7 - date.dayOfWeek }) + break + case 'workWeek': + endDate = date.add({ days: 5 - date.dayOfWeek }) + break + case 'month': + endDate = date + .with({ day: 1 }) + .add({ months: viewModeValue }) + .subtract({ days: 1 }) + if (viewModeValue > 1) { + const lastDayOfMonthWeekDay = + (endDate.dayOfWeek - firstDayOfWeek + 7) % 7 + endDate = endDate.add({ days: 6 - lastDayOfMonthWeekDay }) + } + break + case 'year': + endDate = date.with({ month: 12, day: 31 }) + break case 'decade': { - const lastYearOfDecade = date.with({ year: date.year - (date.year % 10) + 9 }); - const lastDayOfDecade = lastYearOfDecade.with({ month: 12, day: 31 }); - return lastDayOfDecade.with({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }); + const lastYearOfDecade = date.with({ + year: date.year - (date.year % 10) + 9, + }) + endDate = lastYearOfDecade.with({ month: 12, day: 31 }) + break } default: - throw new Error(`Unsupported unit: ${unit}`); + throw new Error(`Unsupported unit: ${unit}`) } + + return endDate.with({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + microsecond: 999, + nanosecond: 999, + }) } diff --git a/packages/time/src/utils/startOf.ts b/packages/time/src/utils/startOf.ts index 3cd6c87..67c492b 100644 --- a/packages/time/src/utils/startOf.ts +++ b/packages/time/src/utils/startOf.ts @@ -1,67 +1,99 @@ import type { Temporal } from '@js-temporal/polyfill' +type ViewUnit = 'month' | 'week' | 'day' | 'workWeek' | 'decade' | 'year' + +interface StartOfParams { + date: Temporal.ZonedDateTime + unit: ViewUnit + firstDayOfWeek?: number +} + /** * Helper function to get the start of a given temporal unit. - * @param date {Temporal.ZonedDateTime} - The date for which the start of the unit is needed. - * @param unit {Unit} - The unit for which to find the start ('day', 'week', 'month', 'year', 'workWeek', 'decade'). + * @param {StartOfParams} params - The parameters for the startOf function. * @returns {Temporal.ZonedDateTime} The start of the given unit. */ -export const startOf = (date: Temporal.ZonedDateTime, unit: 'day' | 'week' | 'month' | 'year' | 'workWeek' | 'decade'): Temporal.ZonedDateTime => { +export function startOf({ + date, + unit, + firstDayOfWeek = 1 +}: StartOfParams): Temporal.ZonedDateTime { + let startDate: Temporal.ZonedDateTime + switch (unit) { case 'day': - return date.with({ + startDate = date.with({ hour: 0, minute: 0, second: 0, millisecond: 0, - }); + microsecond: 0, + nanosecond: 0 + }) + break case 'week': { - const startOfWeek = date.subtract({ days: date.dayOfWeek - 1 }).with({ + const daysToSubtract = (date.dayOfWeek - firstDayOfWeek + 7) % 7 + startDate = date.subtract({ days: daysToSubtract }).with({ hour: 0, minute: 0, second: 0, millisecond: 0, - }); - return startOfWeek; + microsecond: 0, + nanosecond: 0 + }) + break } - case 'month': { - const startOfMonth = date.with({ day: 1 }).with({ + case 'month': + startDate = date.with({ + day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, - }); - return startOfMonth; - } - case 'year': { - const startOfYear = date.with({ month: 1, day: 1 }).with({ + microsecond: 0, + nanosecond: 0 + }) + break + case 'year': + startDate = date.with({ + month: 1, + day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, - }); - return startOfYear; - } + microsecond: 0, + nanosecond: 0 + }) + break case 'workWeek': { - const dayOfWeek = date.dayOfWeek; - const startOfWorkWeek = dayOfWeek === 1 ? date : date.subtract({ days: dayOfWeek - 1 }); - return startOfWorkWeek.with({ + const daysToSubtract = (date.dayOfWeek - 1 + 7) % 7 + startDate = date.subtract({ days: daysToSubtract }).with({ hour: 0, minute: 0, second: 0, millisecond: 0, - }); + microsecond: 0, + nanosecond: 0 + }) + break } - case 'decade': { - const startOfDecade = date.with({ year: date.year - (date.year % 10), month: 1, day: 1 }).with({ + case 'decade': + startDate = date.with({ + year: date.year - (date.year % 10), + month: 1, + day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, - }); - return startOfDecade; - } + microsecond: 0, + nanosecond: 0 + }) + break default: - throw new Error(`Unsupported unit: ${unit}`); + throw new Error(`Unsupported unit: ${unit}`) } -} + + return startDate +} \ No newline at end of file