@react-stately/datepicker 3.0.0-rc.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +430 -43
- package/dist/main.js.map +1 -1
- package/dist/module.js +430 -43
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +7 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/placeholders.ts +108 -0
- package/src/useDateFieldState.ts +42 -24
- package/src/useDatePickerState.ts +1 -1
- package/src/useDateRangePickerState.ts +5 -23
package/src/useDateFieldState.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import {Calendar, DateFormatter, getMinimumDayInMonth, getMinimumMonthInYear, GregorianCalendar, toCalendar} from '@internationalized/date';
|
|
14
14
|
import {convertValue, createPlaceholderDate, FieldOptions, getFormatOptions, isInvalid, useDefaultProps} from './utils';
|
|
15
15
|
import {DatePickerProps, DateValue, Granularity} from '@react-types/datepicker';
|
|
16
|
+
import {getPlaceholder} from './placeholders';
|
|
16
17
|
import {useControlledState} from '@react-stately/utils';
|
|
17
18
|
import {useEffect, useMemo, useRef, useState} from 'react';
|
|
18
19
|
import {ValidationState} from '@react-types/shared';
|
|
@@ -31,6 +32,8 @@ export interface DateSegment {
|
|
|
31
32
|
maxValue?: number,
|
|
32
33
|
/** Whether the value is a placeholder. */
|
|
33
34
|
isPlaceholder: boolean,
|
|
35
|
+
/** A placeholder string for the segment. */
|
|
36
|
+
placeholder: string,
|
|
34
37
|
/** Whether the segment is editable. */
|
|
35
38
|
isEditable: boolean
|
|
36
39
|
}
|
|
@@ -40,6 +43,8 @@ export interface DateFieldState {
|
|
|
40
43
|
value: DateValue,
|
|
41
44
|
/** The current value, converted to a native JavaScript `Date` object. */
|
|
42
45
|
dateValue: Date,
|
|
46
|
+
/** The calendar system currently in use. */
|
|
47
|
+
calendar: Calendar,
|
|
43
48
|
/** Sets the field's value. */
|
|
44
49
|
setValue(value: DateValue): void,
|
|
45
50
|
/** A list of segments for the current value. */
|
|
@@ -75,12 +80,10 @@ export interface DateFieldState {
|
|
|
75
80
|
*/
|
|
76
81
|
decrementPage(type: SegmentType): void,
|
|
77
82
|
/** Sets the value of the given segment. */
|
|
83
|
+
setSegment(type: 'era', value: string): void,
|
|
78
84
|
setSegment(type: SegmentType, value: number): void,
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
* If a segment type is provided, only that segment is confirmed. Otherwise, all segments that have not been entered yet are confirmed.
|
|
82
|
-
*/
|
|
83
|
-
confirmPlaceholder(type?: SegmentType): void,
|
|
85
|
+
/** Updates the remaining unfilled segments with the placeholder value. */
|
|
86
|
+
confirmPlaceholder(): void,
|
|
84
87
|
/** Clears the value of the given segment, reverting it to the placeholder. */
|
|
85
88
|
clearSegment(type: SegmentType): void,
|
|
86
89
|
/** Formats the current date value using the given options. */
|
|
@@ -189,7 +192,7 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
|
|
|
189
192
|
|
|
190
193
|
// Determine how many editable segments there are for validation purposes.
|
|
191
194
|
// The result is cached for performance.
|
|
192
|
-
let allSegments = useMemo(() =>
|
|
195
|
+
let allSegments: Partial<typeof EDITABLE_SEGMENTS> = useMemo(() =>
|
|
193
196
|
dateFormatter.formatToParts(new Date())
|
|
194
197
|
.filter(seg => EDITABLE_SEGMENTS[seg.type])
|
|
195
198
|
.reduce((p, seg) => (p[seg.type] = true, p), {})
|
|
@@ -251,29 +254,46 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
|
|
|
251
254
|
isEditable = false;
|
|
252
255
|
}
|
|
253
256
|
|
|
257
|
+
let isPlaceholder = EDITABLE_SEGMENTS[segment.type] && !validSegments[segment.type];
|
|
258
|
+
let placeholder = EDITABLE_SEGMENTS[segment.type] ? getPlaceholder(segment.type, segment.value, locale) : null;
|
|
254
259
|
return {
|
|
255
260
|
type: TYPE_MAPPING[segment.type] || segment.type,
|
|
256
|
-
text: segment.value,
|
|
261
|
+
text: isPlaceholder ? placeholder : segment.value,
|
|
257
262
|
...getSegmentLimits(displayValue, segment.type, resolvedOptions),
|
|
258
|
-
isPlaceholder
|
|
263
|
+
isPlaceholder,
|
|
264
|
+
placeholder,
|
|
259
265
|
isEditable
|
|
260
266
|
} as DateSegment;
|
|
261
267
|
})
|
|
262
|
-
, [dateValue, validSegments, dateFormatter, resolvedOptions, displayValue, calendar]);
|
|
268
|
+
, [dateValue, validSegments, dateFormatter, resolvedOptions, displayValue, calendar, locale]);
|
|
263
269
|
|
|
264
|
-
|
|
270
|
+
// When the era field appears, mark it valid if the year field is already valid.
|
|
271
|
+
// If the era field disappears, remove it from the valid segments.
|
|
272
|
+
if (allSegments.era && validSegments.year && !validSegments.era) {
|
|
273
|
+
validSegments.era = true;
|
|
274
|
+
setValidSegments({...validSegments});
|
|
275
|
+
} else if (!allSegments.era && validSegments.era) {
|
|
276
|
+
delete validSegments.era;
|
|
277
|
+
setValidSegments({...validSegments});
|
|
278
|
+
}
|
|
265
279
|
|
|
266
280
|
let markValid = (part: Intl.DateTimeFormatPartTypes) => {
|
|
267
281
|
validSegments[part] = true;
|
|
268
|
-
if (part === 'year' &&
|
|
282
|
+
if (part === 'year' && allSegments.era) {
|
|
269
283
|
validSegments.era = true;
|
|
270
284
|
}
|
|
271
285
|
setValidSegments({...validSegments});
|
|
272
286
|
};
|
|
273
287
|
|
|
274
288
|
let adjustSegment = (type: Intl.DateTimeFormatPartTypes, amount: number) => {
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
if (!validSegments[type]) {
|
|
290
|
+
markValid(type);
|
|
291
|
+
if (Object.keys(validSegments).length >= Object.keys(allSegments).length) {
|
|
292
|
+
setValue(displayValue);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
setValue(addSegment(displayValue, type, amount, resolvedOptions));
|
|
296
|
+
}
|
|
277
297
|
};
|
|
278
298
|
|
|
279
299
|
let validationState: ValidationState = props.validationState ||
|
|
@@ -282,6 +302,7 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
|
|
|
282
302
|
return {
|
|
283
303
|
value: calendarValue,
|
|
284
304
|
dateValue,
|
|
305
|
+
calendar,
|
|
285
306
|
setValue,
|
|
286
307
|
segments,
|
|
287
308
|
dateFormatter,
|
|
@@ -307,21 +328,17 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
|
|
|
307
328
|
markValid(part);
|
|
308
329
|
setValue(setSegment(displayValue, part, v, resolvedOptions));
|
|
309
330
|
},
|
|
310
|
-
confirmPlaceholder(
|
|
331
|
+
confirmPlaceholder() {
|
|
311
332
|
if (props.isDisabled || props.isReadOnly) {
|
|
312
333
|
return;
|
|
313
334
|
}
|
|
314
335
|
|
|
315
|
-
if
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
setValue(displayValue.copy());
|
|
322
|
-
}
|
|
323
|
-
} else if (!validSegments[part]) {
|
|
324
|
-
markValid(part);
|
|
336
|
+
// Confirm the placeholder if only the day period is not filled in.
|
|
337
|
+
let validKeys = Object.keys(validSegments);
|
|
338
|
+
let allKeys = Object.keys(allSegments);
|
|
339
|
+
if (validKeys.length === allKeys.length - 1 && allSegments.dayPeriod && !validSegments.dayPeriod) {
|
|
340
|
+
validSegments = {...allSegments};
|
|
341
|
+
setValidSegments(validSegments);
|
|
325
342
|
setValue(displayValue.copy());
|
|
326
343
|
}
|
|
327
344
|
},
|
|
@@ -463,6 +480,7 @@ function setSegment(value: DateValue, part: string, segmentValue: number, option
|
|
|
463
480
|
case 'day':
|
|
464
481
|
case 'month':
|
|
465
482
|
case 'year':
|
|
483
|
+
case 'era':
|
|
466
484
|
return value.set({[part]: segmentValue});
|
|
467
485
|
}
|
|
468
486
|
|
|
@@ -71,7 +71,7 @@ export function useDatePickerState(props: DatePickerStateOptions): DatePickerSta
|
|
|
71
71
|
let v = (value || props.placeholderValue);
|
|
72
72
|
let [granularity, defaultTimeZone] = useDefaultProps(v, props.granularity);
|
|
73
73
|
let dateValue = value != null ? value.toDate(defaultTimeZone ?? 'UTC') : null;
|
|
74
|
-
let hasTime = granularity === 'hour' || granularity === 'minute' || granularity === 'second'
|
|
74
|
+
let hasTime = granularity === 'hour' || granularity === 'minute' || granularity === 'second';
|
|
75
75
|
let shouldCloseOnSelect = props.shouldCloseOnSelect ?? true;
|
|
76
76
|
|
|
77
77
|
let [selectedDate, setSelectedDate] = useState<DateValue>(null);
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {createPlaceholderDate, FieldOptions, getFormatOptions, getPlaceholderTime, isInvalid, useDefaultProps} from './utils';
|
|
14
13
|
import {DateFormatter, toCalendarDate, toCalendarDateTime} from '@internationalized/date';
|
|
15
14
|
import {DateRange, DateRangePickerProps, DateValue, Granularity, TimeValue} from '@react-types/datepicker';
|
|
15
|
+
import {FieldOptions, getFormatOptions, getPlaceholderTime, isInvalid, useDefaultProps} from './utils';
|
|
16
16
|
import {RangeValue, ValidationState} from '@react-types/shared';
|
|
17
17
|
import {useControlledState} from '@react-stately/utils';
|
|
18
18
|
import {useOverlayTriggerState} from '@react-stately/overlays';
|
|
19
|
-
import {
|
|
19
|
+
import {useState} from 'react';
|
|
20
20
|
|
|
21
21
|
export interface DateRangePickerStateOptions extends DateRangePickerProps<DateValue> {
|
|
22
22
|
/**
|
|
@@ -63,9 +63,7 @@ export interface DateRangePickerState {
|
|
|
63
63
|
/** The current validation state of the date picker, based on the `validationState`, `minValue`, and `maxValue` props. */
|
|
64
64
|
validationState: ValidationState,
|
|
65
65
|
/** Formats the selected range using the given options. */
|
|
66
|
-
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string}
|
|
67
|
-
/** Replaces the start and/or end value of the selected range with the placeholder value if unentered. */
|
|
68
|
-
confirmPlaceholder(): void
|
|
66
|
+
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string}
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
/**
|
|
@@ -85,11 +83,8 @@ export function useDateRangePickerState(props: DateRangePickerStateOptions): Dat
|
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
let value = controlledValue || placeholderValue;
|
|
88
|
-
let valueRef = useRef(value);
|
|
89
|
-
valueRef.current = value;
|
|
90
86
|
|
|
91
87
|
let setValue = (value: DateRange) => {
|
|
92
|
-
valueRef.current = value;
|
|
93
88
|
setPlaceholderValue(value);
|
|
94
89
|
if (value?.start && value.end) {
|
|
95
90
|
setControlledValue(value);
|
|
@@ -99,8 +94,8 @@ export function useDateRangePickerState(props: DateRangePickerStateOptions): Dat
|
|
|
99
94
|
};
|
|
100
95
|
|
|
101
96
|
let v = (value?.start || value?.end || props.placeholderValue);
|
|
102
|
-
let [granularity
|
|
103
|
-
let hasTime = granularity === 'hour' || granularity === 'minute' || granularity === 'second'
|
|
97
|
+
let [granularity] = useDefaultProps(v, props.granularity);
|
|
98
|
+
let hasTime = granularity === 'hour' || granularity === 'minute' || granularity === 'second';
|
|
104
99
|
let shouldCloseOnSelect = props.shouldCloseOnSelect ?? true;
|
|
105
100
|
|
|
106
101
|
let [dateRange, setSelectedDateRange] = useState<DateRange>(null);
|
|
@@ -268,19 +263,6 @@ export function useDateRangePickerState(props: DateRangePickerStateOptions): Dat
|
|
|
268
263
|
start: startFormatter.format(startDate),
|
|
269
264
|
end: endFormatter.format(endDate)
|
|
270
265
|
};
|
|
271
|
-
},
|
|
272
|
-
confirmPlaceholder() {
|
|
273
|
-
// Need to use ref value here because the value can be set in the same tick as
|
|
274
|
-
// a blur, which means the component won't have re-rendered yet.
|
|
275
|
-
let value = valueRef.current;
|
|
276
|
-
if (value && Boolean(value.start) !== Boolean(value.end)) {
|
|
277
|
-
let calendar = (value.start || value.end).calendar;
|
|
278
|
-
let placeholder = createPlaceholderDate(props.placeholderValue, granularity, calendar, defaultTimeZone);
|
|
279
|
-
setValue({
|
|
280
|
-
start: value.start || placeholder,
|
|
281
|
-
end: value.end || placeholder
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
266
|
}
|
|
285
267
|
};
|
|
286
268
|
}
|