Skip to content

Commit 7238093

Browse files
authored
TimeField with defaultValue persists time zone (#4715)
* improving the influence of defaultValue's timeZone on TimeField * debugging console messages * reverting all the console logging * adding another timeZone check * added tests that cover the bug and the secondary issue
1 parent 39850f0 commit 7238093

File tree

2 files changed

+68
-4
lines changed

2 files changed

+68
-4
lines changed

packages/@react-spectrum/datepicker/test/TimeField.test.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
*/
1212

1313
import {act, fireEvent, render as render_, within} from '@react-spectrum/test-utils';
14+
import {parseZonedDateTime, Time} from '@internationalized/date';
1415
import {Provider} from '@react-spectrum/provider';
1516
import React from 'react';
1617
import {theme} from '@react-spectrum/theme-default';
17-
import {Time} from '@internationalized/date';
1818
import {TimeField} from '../';
1919
import userEvent from '@testing-library/user-event';
2020

@@ -176,5 +176,64 @@ describe('TimeField', function () {
176176
expect(onKeyDownSpy).toHaveBeenCalledTimes(1);
177177
expect(onKeyUpSpy).toHaveBeenCalledTimes(2);
178178
});
179+
180+
describe('timeZone', function () {
181+
it('should have a timeZone when set by defaultValue', function () {
182+
let {getByRole} = render(<TimeField label="Time" defaultValue={parseZonedDateTime('2023-07-01T00:45-07:00[America/Los_Angeles]')} />);
183+
let timezone = getByRole('textbox');
184+
185+
expect(timezone.getAttribute('aria-label')).toBe('time zone, ');
186+
expect(within(timezone).getByText('PDT')).toBeInTheDocument();
187+
});
188+
189+
it('should keep timeZone from defaultValue when minute segment cleared', function () {
190+
let {getAllByRole, getByRole} = render(<TimeField label="Time" defaultValue={parseZonedDateTime('2023-07-01T01:05-07:00[America/Los_Angeles]')} />);
191+
let timezone = getByRole('textbox');
192+
let segments = getAllByRole('spinbutton');
193+
194+
userEvent.tab();
195+
expect(segments[0]).toHaveFocus();
196+
197+
userEvent.tab();
198+
expect(segments[1]).toHaveFocus();
199+
expect(segments[1]).toHaveAttribute('aria-valuetext', '05');
200+
expect(timezone.getAttribute('aria-label')).toBe('time zone, ');
201+
expect(within(timezone).getByText('PDT')).toBeInTheDocument();
202+
203+
fireEvent.keyDown(document.activeElement, {key: 'Backspace'});
204+
fireEvent.keyUp(document.activeElement, {key: 'Backspace'});
205+
expect(segments[1]).toHaveAttribute('aria-valuetext', 'Empty');
206+
expect(timezone.getAttribute('aria-label')).toBe('time zone, ');
207+
expect(within(timezone).getByText('PDT')).toBeInTheDocument();
208+
});
209+
210+
it('should keep timeZone from defaultValue when minute segment cleared then set', function () {
211+
let {getAllByRole, getByRole} = render(<TimeField label="Time" defaultValue={parseZonedDateTime('2023-07-01T01:05-07:00[America/Los_Angeles]')} />);
212+
let timezone = getByRole('textbox');
213+
let segments = getAllByRole('spinbutton');
214+
215+
userEvent.tab();
216+
expect(segments[0]).toHaveFocus();
217+
218+
userEvent.tab();
219+
expect(segments[1]).toHaveFocus();
220+
expect(segments[1]).toHaveAttribute('aria-valuetext', '05');
221+
expect(timezone.getAttribute('aria-label')).toBe('time zone, ');
222+
expect(within(timezone).getByText('PDT')).toBeInTheDocument();
223+
224+
fireEvent.keyDown(document.activeElement, {key: 'Backspace'});
225+
fireEvent.keyUp(document.activeElement, {key: 'Backspace'});
226+
expect(segments[1]).toHaveFocus();
227+
expect(segments[1]).toHaveAttribute('aria-valuetext', 'Empty');
228+
expect(timezone.getAttribute('aria-label')).toBe('time zone, ');
229+
expect(within(timezone).getByText('PDT')).toBeInTheDocument();
230+
231+
fireEvent.keyDown(document.activeElement, {key: 'ArrowUp'});
232+
fireEvent.keyUp(document.activeElement, {key: 'ArrowUp'});
233+
expect(segments[1]).toHaveAttribute('aria-valuetext', '00');
234+
expect(timezone.getAttribute('aria-label')).toBe('time zone, ');
235+
expect(within(timezone).getByText('PDT')).toBeInTheDocument();
236+
});
237+
});
179238
});
180239
});

packages/@react-stately/datepicker/src/useTimeFieldState.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import {DateFieldState, useDateFieldState} from '.';
1414
import {DateValue, TimePickerProps, TimeValue} from '@react-types/datepicker';
15-
import {getLocalTimeZone, GregorianCalendar, Time, toCalendarDateTime, today, toTime} from '@internationalized/date';
15+
import {getLocalTimeZone, GregorianCalendar, Time, toCalendarDateTime, today, toTime, toZoned} from '@internationalized/date';
1616
import {useControlledState} from '@react-stately/utils';
1717
import {useMemo} from 'react';
1818

@@ -42,13 +42,18 @@ export function useTimeFieldState<T extends TimeValue = TimeValue>(props: TimeFi
4242

4343
let v = value || placeholderValue;
4444
let day = v && 'day' in v ? v : undefined;
45-
let placeholderDate = useMemo(() => convertValue(placeholderValue), [placeholderValue]);
45+
let defaultValueTimeZone = props.defaultValue && 'timeZone' in props.defaultValue ? props.defaultValue.timeZone : undefined;
46+
let placeholderDate = useMemo(() => {
47+
let valueTimeZone = v && 'timeZone' in v ? v.timeZone : undefined;
48+
49+
return valueTimeZone || defaultValueTimeZone ? toZoned(convertValue(placeholderValue), valueTimeZone || defaultValueTimeZone) : convertValue(placeholderValue);
50+
}, [placeholderValue]);
4651
let minDate = useMemo(() => convertValue(minValue, day), [minValue, day]);
4752
let maxDate = useMemo(() => convertValue(maxValue, day), [maxValue, day]);
4853

4954
let dateTime = useMemo(() => value == null ? null : convertValue(value), [value]);
5055
let onChange = newValue => {
51-
setValue(v && 'day' in v ? newValue : newValue && toTime(newValue));
56+
setValue(day || defaultValueTimeZone ? newValue : newValue && toTime(newValue));
5257
};
5358

5459
return useDateFieldState({

0 commit comments

Comments
 (0)