Skip to content

Commit ad56ac0

Browse files
[pickers] Use props.referenceDate timezone when props.value and props.defaultValue are not defined (@flaviendelangle) (#15544)
Co-authored-by: Flavien DELANGLE <[email protected]>
1 parent ead5d30 commit ad56ac0

File tree

16 files changed

+178
-26
lines changed

16 files changed

+178
-26
lines changed

packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,14 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar<
208208
} = props;
209209

210210
const { value, handleValueChange, timezone } = useControlledValueWithTimezone<
211-
TDate,
212211
DateRange<TDate>,
212+
TDate,
213213
NonNullable<typeof onChange>
214214
>({
215215
name: 'DateRangeCalendar',
216216
timezone: timezoneProp,
217217
value: valueProp,
218+
referenceDate,
218219
defaultValue,
219220
onChange,
220221
valueManager: rangeValueManager,

packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const useMultiInputDateRangeField = <
4747
const {
4848
value: valueProp,
4949
defaultValue,
50+
referenceDate,
5051
format,
5152
formatDensity,
5253
shouldRespectLeadingZeros,
@@ -65,6 +66,7 @@ export const useMultiInputDateRangeField = <
6566
timezone: timezoneProp,
6667
value: valueProp,
6768
defaultValue,
69+
referenceDate,
6870
onChange,
6971
valueManager: rangeValueManager,
7072
});

packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const useMultiInputDateTimeRangeField = <
4747
const {
4848
value: valueProp,
4949
defaultValue,
50+
referenceDate,
5051
format,
5152
formatDensity,
5253
shouldRespectLeadingZeros,
@@ -65,6 +66,7 @@ export const useMultiInputDateTimeRangeField = <
6566
timezone: timezoneProp,
6667
value: valueProp,
6768
defaultValue,
69+
referenceDate,
6870
onChange,
6971
valueManager: rangeValueManager,
7072
});

packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const useMultiInputTimeRangeField = <
4747
const {
4848
value: valueProp,
4949
defaultValue,
50+
referenceDate,
5051
format,
5152
formatDensity,
5253
shouldRespectLeadingZeros,
@@ -67,6 +68,7 @@ export const useMultiInputTimeRangeField = <
6768
defaultValue,
6869
onChange,
6970
valueManager: rangeValueManager,
71+
referenceDate,
7072
});
7173

7274
const { validationError, getValidationErrorForNewValue } = useValidation({

packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export const DateCalendar = React.forwardRef(function DateCalendar<TDate extends
154154
timezone: timezoneProp,
155155
value: valueProp,
156156
defaultValue,
157+
referenceDate: referenceDateProp,
157158
onChange,
158159
valueManager: singleItemValueManager,
159160
});

packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export const DigitalClock = React.forwardRef(function DigitalClock<TDate extends
163163
timezone: timezoneProp,
164164
value: valueProp,
165165
defaultValue,
166+
referenceDate: referenceDateProp,
166167
onChange,
167168
valueManager: singleItemValueManager,
168169
});

packages/x-date-pickers/src/MonthCalendar/MonthCalendar.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ export const MonthCalendar = React.forwardRef(function MonthCalendar<TDate exten
117117
timezone: timezoneProp,
118118
value: valueProp,
119119
defaultValue,
120-
onChange: onChange as (value: TDate | null) => void,
120+
referenceDate: referenceDateProp,
121+
onChange,
121122
valueManager: singleItemValueManager,
122123
});
123124

packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi
112112
timezone: timezoneProp,
113113
value: valueProp,
114114
defaultValue,
115+
referenceDate: referenceDateProp,
115116
onChange,
116117
valueManager: singleItemValueManager,
117118
});

packages/x-date-pickers/src/TimeClock/TimeClock.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export const TimeClock = React.forwardRef(function TimeClock<TDate extends Picke
114114
timezone: timezoneProp,
115115
value: valueProp,
116116
defaultValue,
117+
referenceDate: referenceDateProp,
117118
onChange,
118119
valueManager: singleItemValueManager,
119120
});

packages/x-date-pickers/src/YearCalendar/YearCalendar.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ export const YearCalendar = React.forwardRef(function YearCalendar<TDate extends
126126
timezone: timezoneProp,
127127
value: valueProp,
128128
defaultValue,
129-
onChange: onChange as (value: TDate | null) => void,
129+
referenceDate: referenceDateProp,
130+
onChange,
130131
valueManager: singleItemValueManager,
131132
});
132133

packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export const useFieldState = <
120120
timezone: timezoneProp,
121121
value: valueProp,
122122
defaultValue,
123+
referenceDate: referenceDateProp,
123124
onChange,
124125
valueManager,
125126
});

packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface UsePickerProps<
3535
TError,
3636
TExternalProps extends UsePickerViewsProps<TValue, TDate, TView, any, any>,
3737
TAdditionalProps extends {},
38-
> extends UsePickerValueProps<TValue, TError>,
38+
> extends UsePickerValueProps<TValue, TDate, TError>,
3939
UsePickerViewsProps<TValue, TDate, TView, TExternalProps, TAdditionalProps>,
4040
UsePickerLayoutProps {}
4141

packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export const usePickerValue = <
154154
TValue,
155155
TDate extends PickerValidDate,
156156
TSection extends FieldSection,
157-
TExternalProps extends UsePickerValueProps<TValue, any>,
157+
TExternalProps extends UsePickerValueProps<TValue, TDate, any>,
158158
>({
159159
props,
160160
valueManager,
@@ -175,6 +175,7 @@ export const usePickerValue = <
175175
defaultValue: inDefaultValue,
176176
closeOnSelect = wrapperVariant === 'desktop',
177177
timezone: timezoneProp,
178+
referenceDate,
178179
} = props;
179180

180181
const { current: defaultValue } = React.useRef(inDefaultValue);
@@ -225,6 +226,7 @@ export const usePickerValue = <
225226
timezone: timezoneProp,
226227
value: inValueWithoutRenderTimezone,
227228
defaultValue,
229+
referenceDate,
228230
onChange,
229231
valueManager,
230232
});

packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -267,15 +267,18 @@ export interface UsePickerValueNonStaticProps {
267267
/**
268268
* Props used to handle the value of the pickers.
269269
*/
270-
export interface UsePickerValueProps<TValue, TError>
270+
export interface UsePickerValueProps<TValue, TDate extends PickerValidDate, TError>
271271
extends UsePickerValueBaseProps<TValue, TError>,
272272
UsePickerValueNonStaticProps,
273-
TimezoneProps {}
273+
TimezoneProps {
274+
// We don't add JSDoc here because we want the `referenceDate` JSDoc to be the one from the view which has more context.
275+
referenceDate?: TDate;
276+
}
274277

275278
export interface UsePickerValueParams<
276279
TValue,
277280
TDate extends PickerValidDate,
278-
TExternalProps extends UsePickerValueProps<TValue, any>,
281+
TExternalProps extends UsePickerValueProps<TValue, TDate, any>,
279282
> {
280283
props: TExternalProps;
281284
valueManager: PickerValueManager<TValue, TDate, InferError<TExternalProps>>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import * as React from 'react';
2+
import { expect } from 'chai';
3+
import { screen } from '@mui/internal-test-utils';
4+
import { PickersTimezone, PickerValidDate } from '@mui/x-date-pickers/models';
5+
import { createPickerRenderer } from 'test/utils/pickers';
6+
import { useValueWithTimezone } from './useValueWithTimezone';
7+
import { singleItemValueManager } from '../utils/valueManagers';
8+
9+
describe('useValueWithTimezone', () => {
10+
const { render, adapter } = createPickerRenderer({
11+
clock: 'fake',
12+
adapterName: 'luxon',
13+
});
14+
15+
function runTest(params: {
16+
timezone: PickersTimezone | undefined;
17+
value: PickerValidDate | null | undefined;
18+
defaultValue: PickerValidDate | null | undefined;
19+
referenceDate: PickerValidDate | undefined;
20+
expectedTimezone: PickersTimezone;
21+
}) {
22+
const { expectedTimezone, ...other } = params;
23+
24+
function TestComponent(props: typeof other) {
25+
const { timezone } = useValueWithTimezone({
26+
...props,
27+
valueManager: singleItemValueManager,
28+
onChange: () => {},
29+
});
30+
31+
return <div data-testid="result">{timezone}</div>;
32+
}
33+
34+
render(<TestComponent {...other} />);
35+
36+
expect(screen.getByTestId('result').textContent).to.equal(expectedTimezone);
37+
}
38+
39+
it('should use the timezone parameter when provided', () => {
40+
runTest({
41+
timezone: 'America/New_York',
42+
value: undefined,
43+
defaultValue: undefined,
44+
referenceDate: undefined,
45+
expectedTimezone: 'America/New_York',
46+
});
47+
});
48+
49+
it('should use the timezone parameter over the value parameter when both are provided', () => {
50+
runTest({
51+
timezone: 'America/New_York',
52+
value: adapter.date(undefined, 'Europe/Paris'),
53+
defaultValue: undefined,
54+
referenceDate: undefined,
55+
expectedTimezone: 'America/New_York',
56+
});
57+
});
58+
59+
it('should use the value parameter when provided', () => {
60+
runTest({
61+
timezone: undefined,
62+
value: adapter.date(undefined, 'America/New_York'),
63+
defaultValue: undefined,
64+
referenceDate: undefined,
65+
expectedTimezone: 'America/New_York',
66+
});
67+
});
68+
69+
it('should use the value parameter over the defaultValue parameter when both are provided', () => {
70+
runTest({
71+
timezone: undefined,
72+
value: adapter.date(undefined, 'America/New_York'),
73+
defaultValue: adapter.date(undefined, 'Europe/Paris'),
74+
referenceDate: undefined,
75+
expectedTimezone: 'America/New_York',
76+
});
77+
});
78+
79+
it('should use the defaultValue parameter when provided', () => {
80+
runTest({
81+
timezone: undefined,
82+
value: undefined,
83+
defaultValue: adapter.date(undefined, 'America/New_York'),
84+
referenceDate: undefined,
85+
expectedTimezone: 'America/New_York',
86+
});
87+
});
88+
89+
it('should use the referenceDate parameter when provided', () => {
90+
runTest({
91+
timezone: undefined,
92+
value: undefined,
93+
defaultValue: undefined,
94+
referenceDate: adapter.date(undefined, 'America/New_York'),
95+
expectedTimezone: 'America/New_York',
96+
});
97+
});
98+
99+
it('should use the "default" timezone is there is no way to deduce the user timezone', () => {
100+
runTest({
101+
timezone: undefined,
102+
value: undefined,
103+
defaultValue: undefined,
104+
referenceDate: undefined,
105+
expectedTimezone: 'default',
106+
});
107+
});
108+
});

packages/x-date-pickers/src/internals/hooks/useValueWithTimezone.ts

+43-18
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,17 @@ import { PickersTimezone, PickerValidDate } from '../../models';
1111
* - The value rendered is always the one from `props.timezone` if defined
1212
*/
1313
export const useValueWithTimezone = <
14-
TDate extends PickerValidDate,
1514
TValue,
15+
TDate extends PickerValidDate,
1616
TChange extends (...params: any[]) => void,
1717
>({
1818
timezone: timezoneProp,
1919
value: valueProp,
2020
defaultValue,
21+
referenceDate,
2122
onChange,
2223
valueManager,
23-
}: {
24-
timezone: PickersTimezone | undefined;
25-
value: TValue | undefined;
26-
defaultValue: TValue | undefined;
27-
onChange: TChange | undefined;
28-
valueManager: PickerValueManager<TValue, TDate, any>;
29-
}) => {
24+
}: UseValueWithTimezoneParameters<TValue, TDate, TChange>) => {
3025
const utils = useUtils<TDate>();
3126

3227
const firstDefaultValue = React.useRef(defaultValue);
@@ -45,7 +40,16 @@ export const useValueWithTimezone = <
4540
return valueManager.setTimezone(utils, inputTimezone, newValue);
4641
});
4742

48-
const timezoneToRender = timezoneProp ?? inputTimezone ?? 'default';
43+
let timezoneToRender: PickersTimezone;
44+
if (timezoneProp) {
45+
timezoneToRender = timezoneProp;
46+
} else if (inputTimezone) {
47+
timezoneToRender = inputTimezone;
48+
} else if (referenceDate) {
49+
timezoneToRender = utils.getTimezone(referenceDate);
50+
} else {
51+
timezoneToRender = 'default';
52+
}
4953

5054
const valueWithTimezoneToRender = React.useMemo(
5155
() => valueManager.setTimezone(utils, timezoneToRender, inputValue),
@@ -64,24 +68,18 @@ export const useValueWithTimezone = <
6468
* Wrapper around `useControlled` and `useValueWithTimezone`
6569
*/
6670
export const useControlledValueWithTimezone = <
67-
TDate extends PickerValidDate,
6871
TValue,
72+
TDate extends PickerValidDate,
6973
TChange extends (...params: any[]) => void,
7074
>({
7175
name,
7276
timezone: timezoneProp,
7377
value: valueProp,
7478
defaultValue,
79+
referenceDate,
7580
onChange: onChangeProp,
7681
valueManager,
77-
}: {
78-
name: string;
79-
timezone: PickersTimezone | undefined;
80-
value: TValue | undefined;
81-
defaultValue: TValue | undefined;
82-
onChange: TChange | undefined;
83-
valueManager: PickerValueManager<TValue, TDate, any>;
84-
}) => {
82+
}: UseControlledValueWithTimezoneParameters<TValue, TDate, TChange>) => {
8583
const [valueWithInputTimezone, setValue] = useControlled({
8684
name,
8785
state: 'value',
@@ -98,7 +96,34 @@ export const useControlledValueWithTimezone = <
9896
timezone: timezoneProp,
9997
value: valueWithInputTimezone,
10098
defaultValue: undefined,
99+
referenceDate,
101100
onChange,
102101
valueManager,
103102
});
104103
};
104+
105+
interface UseValueWithTimezoneParameters<
106+
TValue,
107+
TDate extends PickerValidDate,
108+
TChange extends (...params: any[]) => void,
109+
> {
110+
timezone: PickersTimezone | undefined;
111+
value: TValue | undefined;
112+
defaultValue: TValue | undefined;
113+
/**
114+
* The reference date as passed to `props.referenceDate`.
115+
* It does not need to have its default value.
116+
* This is only used to determine the timezone to use when `props.value` and `props.defaultValue` are not defined.
117+
*/
118+
referenceDate: TDate | undefined;
119+
onChange: TChange | undefined;
120+
valueManager: PickerValueManager<TValue, TDate, any>;
121+
}
122+
123+
interface UseControlledValueWithTimezoneParameters<
124+
TValue,
125+
TDate extends PickerValidDate,
126+
TChange extends (...params: any[]) => void,
127+
> extends UseValueWithTimezoneParameters<TValue, TDate, TChange> {
128+
name: string;
129+
}

0 commit comments

Comments
 (0)