diff --git a/package-lock.json b/package-lock.json index 875687cae0c..12f2d5b46fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,7 +70,7 @@ "pusher-js": "^8.4.0-rc2", "qs": "^6.15.2", "read-time-estimate": "0.0.3", - "reka-ui": "^2.9.2", + "reka-ui": "^2.10.5", "resize-observer-polyfill": "^1.5.1", "semver": "^7.7.1", "speakingurl": "^14.0.1", @@ -1221,9 +1221,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1244,9 +1241,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1267,9 +1261,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1290,9 +1281,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1313,9 +1301,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1559,9 +1544,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1579,9 +1561,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1599,9 +1578,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1619,9 +1595,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1639,9 +1612,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1659,9 +1629,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1679,9 +1646,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1699,9 +1663,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7760,9 +7721,9 @@ } }, "node_modules/reka-ui": { - "version": "2.9.5", - "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.9.5.tgz", - "integrity": "sha512-6cZGIMgEeslpFLJ7IihaCSMPp1cJgl2eDkZ2vBMdl+HPUVBaV/iDPMWu3abT2KUkj1lir+oyHq5KelOTT9OheQ==", + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.10.5.tgz", + "integrity": "sha512-/8o0y9BThE0hcHKFg+3770tWcJh3qKdOEFxLOPb9HIajXeFoH/J530cZH62jUJunbqwBgpSBL9v7nB8LJ5pDXA==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.6.13", diff --git a/package.json b/package.json index ed0bcb9b59d..b7befe44c1d 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "pusher-js": "^8.4.0-rc2", "qs": "^6.15.2", "read-time-estimate": "0.0.3", - "reka-ui": "^2.9.2", + "reka-ui": "^2.10.5", "resize-observer-polyfill": "^1.5.1", "semver": "^7.7.1", "speakingurl": "^14.0.1", diff --git a/resources/js/components/ui/DateRangePicker/DateRangePicker.vue b/resources/js/components/ui/DateRangePicker/DateRangePicker.vue index 14e9e672665..7ff4a97ede3 100644 --- a/resources/js/components/ui/DateRangePicker/DateRangePicker.vue +++ b/resources/js/components/ui/DateRangePicker/DateRangePicker.vue @@ -133,7 +133,7 @@ const hoverCardDate = computed(() => { v-bind="$attrs" prevent-deselect hide-time-zone - :default-placeholder="defaultPlaceholder" + :default-placeholder="(modelValue?.start ?? modelValue?.end) ? undefined : defaultPlaceholder" close-on-select > diff --git a/resources/js/tests/components/ui/DatePicker.test.js b/resources/js/tests/components/ui/DatePicker.test.js new file mode 100644 index 00000000000..08f27bf91c4 --- /dev/null +++ b/resources/js/tests/components/ui/DatePicker.test.js @@ -0,0 +1,40 @@ +import { mount } from '@vue/test-utils'; +import { expect, test } from 'vitest'; +import { CalendarDateTime, toZoned } from '@internationalized/date'; +import DatePicker from '@/components/ui/DatePicker/DatePicker.vue'; +import { config } from '@api'; + +window.__ = (key) => key; + +config.initialize({ translationLocale: 'en' }); + +const makeDatePicker = (modelValue) => { + return mount(DatePicker, { + props: { modelValue, granularity: 'minute' }, + global: { + mocks: { + $date: { locale: 'en-US' }, + }, + }, + }); +}; + +test('day period reflects the zoned date rather than the browser timezone', () => { + process.env.TZ = 'America/New_York'; + + const datePicker = makeDatePicker(toZoned(new CalendarDateTime(2024, 1, 20, 15, 30), 'Europe/London')); + + expect(datePicker.get('[data-reka-date-field-segment="hour"]').text()).toBe('3'); + expect(datePicker.get('[data-reka-date-field-segment="dayPeriod"]').text()).toBe('PM'); +}); + +test('typing an hour keeps the day period of the zoned date', async () => { + process.env.TZ = 'America/New_York'; + + const modelValue = toZoned(new CalendarDateTime(2024, 1, 20, 15, 30), 'Europe/London'); + const datePicker = makeDatePicker(modelValue); + + await datePicker.get('[data-reka-date-field-segment="hour"]').trigger('keydown', { key: '5' }); + + expect(datePicker.emitted('update:modelValue')[0][0].toString()).toBe(modelValue.set({ hour: 17 }).toString()); +}); diff --git a/resources/js/tests/components/ui/DateRangePicker.test.js b/resources/js/tests/components/ui/DateRangePicker.test.js new file mode 100644 index 00000000000..dcb02368e44 --- /dev/null +++ b/resources/js/tests/components/ui/DateRangePicker.test.js @@ -0,0 +1,62 @@ +import { mount } from '@vue/test-utils'; +import { expect, test } from 'vitest'; +import { CalendarDateTime, toZoned } from '@internationalized/date'; +import DateRangePicker from '@/components/ui/DateRangePicker/DateRangePicker.vue'; +import { config } from '@api'; + +window.__ = (key) => key; + +config.initialize({ translationLocale: 'en' }); + +const makeDateRangePicker = (modelValue) => { + return mount(DateRangePicker, { + props: { modelValue, granularity: 'minute' }, + global: { + mocks: { + $date: { locale: 'en-US' }, + }, + }, + }); +}; + +const segment = (type, part) => `[data-reka-date-range-field-segment-type="${type}"][data-reka-date-field-segment="${part}"]`; + +test('day periods reflect the zoned dates rather than the browser timezone', () => { + process.env.TZ = 'America/New_York'; + + const dateRangePicker = makeDateRangePicker({ + start: toZoned(new CalendarDateTime(2024, 1, 20, 15, 30), 'Europe/London'), + end: toZoned(new CalendarDateTime(2024, 1, 21, 16, 30), 'Europe/London'), + }); + + expect(dateRangePicker.get(segment('start', 'hour')).text()).toBe('3'); + expect(dateRangePicker.get(segment('start', 'dayPeriod')).text()).toBe('PM'); + expect(dateRangePicker.get(segment('end', 'hour')).text()).toBe('4'); + expect(dateRangePicker.get(segment('end', 'dayPeriod')).text()).toBe('PM'); +}); + +test('typing an hour keeps the day period of the zoned dates', async () => { + process.env.TZ = 'America/New_York'; + + const start = toZoned(new CalendarDateTime(2024, 1, 20, 15, 30), 'Europe/London'); + const end = toZoned(new CalendarDateTime(2024, 1, 21, 16, 30), 'Europe/London'); + const dateRangePicker = makeDateRangePicker({ start, end }); + + await dateRangePicker.get(segment('end', 'hour')).trigger('keydown', { key: '5' }); + + const emitted = dateRangePicker.emitted('update:modelValue')[0][0]; + expect(emitted.start.toString()).toBe(start.toString()); + expect(emitted.end.toString()).toBe(end.set({ hour: 17 }).toString()); +}); + +test('day period reflects the zoned date rather than the browser timezone when only the end is set', () => { + process.env.TZ = 'America/New_York'; + + const dateRangePicker = makeDateRangePicker({ + start: undefined, + end: toZoned(new CalendarDateTime(2024, 1, 21, 16, 30), 'Europe/London'), + }); + + expect(dateRangePicker.get(segment('end', 'hour')).text()).toBe('4'); + expect(dateRangePicker.get(segment('end', 'dayPeriod')).text()).toBe('PM'); +});