@react-stately/datepicker 3.15.2 → 3.16.0
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/IncompleteDate.main.js +335 -0
- package/dist/IncompleteDate.main.js.map +1 -0
- package/dist/IncompleteDate.mjs +330 -0
- package/dist/IncompleteDate.module.js +330 -0
- package/dist/IncompleteDate.module.js.map +1 -0
- package/dist/ko-KR.main.js +1 -1
- package/dist/ko-KR.main.js.map +1 -1
- package/dist/ko-KR.mjs +1 -1
- package/dist/ko-KR.module.js +1 -1
- package/dist/ko-KR.module.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/useDateFieldState.main.js +122 -266
- package/dist/useDateFieldState.main.js.map +1 -1
- package/dist/useDateFieldState.mjs +124 -268
- package/dist/useDateFieldState.module.js +124 -268
- package/dist/useDateFieldState.module.js.map +1 -1
- package/package.json +9 -8
- package/src/IncompleteDate.ts +392 -0
- package/src/useDateFieldState.ts +124 -284
package/src/useDateFieldState.ts
CHANGED
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {Calendar, CalendarIdentifier, DateFormatter,
|
|
13
|
+
import {Calendar, CalendarIdentifier, DateFormatter, GregorianCalendar, isEqualCalendar, toCalendar} from '@internationalized/date';
|
|
14
14
|
import {convertValue, createPlaceholderDate, FieldOptions, FormatterOptions, getFormatOptions, getValidationResult, useDefaultProps} from './utils';
|
|
15
15
|
import {DatePickerProps, DateValue, Granularity, MappedDateValue} from '@react-types/datepicker';
|
|
16
16
|
import {FormValidationState, useFormValidationState} from '@react-stately/form';
|
|
17
17
|
import {getPlaceholder} from './placeholders';
|
|
18
|
+
import {IncompleteDate} from './IncompleteDate';
|
|
19
|
+
import {NumberFormatter} from '@internationalized/number';
|
|
18
20
|
import {useControlledState} from '@react-stately/utils';
|
|
19
|
-
import {
|
|
21
|
+
import {useMemo, useState} from 'react';
|
|
20
22
|
import {ValidationState} from '@react-types/shared';
|
|
21
23
|
|
|
22
24
|
export type SegmentType = 'era' | 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second' | 'dayPeriod' | 'literal' | 'timeZoneName';
|
|
@@ -26,7 +28,7 @@ export interface DateSegment {
|
|
|
26
28
|
/** The formatted text for the segment. */
|
|
27
29
|
text: string,
|
|
28
30
|
/** The numeric value for the segment, if applicable. */
|
|
29
|
-
value?: number,
|
|
31
|
+
value?: number | null,
|
|
30
32
|
/** The minimum numeric value for the segment, if applicable. */
|
|
31
33
|
minValue?: number,
|
|
32
34
|
/** The maximum numeric value for the segment, if applicable. */
|
|
@@ -87,6 +89,10 @@ export interface DateFieldState extends FormValidationState {
|
|
|
87
89
|
* Upon reaching the minimum or maximum value, the value wraps around to the opposite limit.
|
|
88
90
|
*/
|
|
89
91
|
decrementPage(type: SegmentType): void,
|
|
92
|
+
/** Increments the given segment to its maxiumum value. */
|
|
93
|
+
incrementToMax(type: SegmentType): void,
|
|
94
|
+
/** Decrements the given segment to its minimum value. */
|
|
95
|
+
decrementToMin(type: SegmentType): void,
|
|
90
96
|
/** Sets the value of the given segment. */
|
|
91
97
|
setSegment(type: 'era', value: string): void,
|
|
92
98
|
setSegment(type: SegmentType, value: number): void,
|
|
@@ -173,8 +179,17 @@ export function useDateFieldState<T extends DateValue = DateValue>(props: DateFi
|
|
|
173
179
|
throw new Error('Invalid granularity ' + granularity + ' for value ' + v.toString());
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
|
|
177
|
-
let calendar = useMemo(() =>
|
|
182
|
+
// Resolve default hour cycle and calendar system.
|
|
183
|
+
let [calendar, hourCycle] = useMemo(() => {
|
|
184
|
+
let formatter = new DateFormatter(locale, {
|
|
185
|
+
dateStyle: 'short',
|
|
186
|
+
timeStyle: 'short',
|
|
187
|
+
hour12: props.hourCycle != null ? props.hourCycle === 12 : undefined
|
|
188
|
+
});
|
|
189
|
+
let opts = formatter.resolvedOptions();
|
|
190
|
+
let calendar = createCalendar(opts.calendar as CalendarIdentifier);
|
|
191
|
+
return [calendar, opts.hourCycle!];
|
|
192
|
+
}, [locale, props.hourCycle, createCalendar]);
|
|
178
193
|
|
|
179
194
|
let [value, setDate] = useControlledState<DateValue | null, MappedDateValue<T> | null>(
|
|
180
195
|
props.value,
|
|
@@ -184,17 +199,11 @@ export function useDateFieldState<T extends DateValue = DateValue>(props: DateFi
|
|
|
184
199
|
|
|
185
200
|
let [initialValue] = useState(value);
|
|
186
201
|
let calendarValue = useMemo(() => convertValue(value, calendar) ?? null, [value, calendar]);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// until all segments are set. If the value === null (not undefined), then assume the component
|
|
190
|
-
// is controlled, so use the placeholder as the value until all segments are entered so it doesn't
|
|
191
|
-
// change from uncontrolled to controlled and emit a warning.
|
|
192
|
-
let [placeholderDate, setPlaceholderDate] = useState(
|
|
193
|
-
() => createPlaceholderDate(props.placeholderValue, granularity, calendar, defaultTimeZone)
|
|
202
|
+
let [displayValue, setDisplayValue] = useState(
|
|
203
|
+
() => new IncompleteDate(calendar, hourCycle, calendarValue)
|
|
194
204
|
);
|
|
195
205
|
|
|
196
|
-
let
|
|
197
|
-
let showEra = calendar.identifier === 'gregory' && val.era === 'BC';
|
|
206
|
+
let showEra = calendar.identifier === 'gregory' && displayValue.era === 'BC';
|
|
198
207
|
let formatOpts = useMemo(() => ({
|
|
199
208
|
granularity,
|
|
200
209
|
maxGranularity: props.maxGranularity ?? 'year',
|
|
@@ -208,117 +217,73 @@ export function useDateFieldState<T extends DateValue = DateValue>(props: DateFi
|
|
|
208
217
|
|
|
209
218
|
let dateFormatter = useMemo(() => new DateFormatter(locale, opts), [locale, opts]);
|
|
210
219
|
let resolvedOptions = useMemo(() => dateFormatter.resolvedOptions(), [dateFormatter]);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
, [
|
|
219
|
-
|
|
220
|
-
let [
|
|
221
|
-
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (!isEqualCalendar(calendar, lastCalendar.current)) {
|
|
230
|
-
lastCalendar.current = calendar;
|
|
231
|
-
setPlaceholderDate(placeholder =>
|
|
232
|
-
Object.keys(validSegments).length > 0
|
|
233
|
-
? toCalendar(placeholder, calendar)
|
|
234
|
-
: createPlaceholderDate(props.placeholderValue, granularity, calendar, defaultTimeZone)
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
}, [calendar, granularity, validSegments, defaultTimeZone, props.placeholderValue]);
|
|
238
|
-
|
|
239
|
-
// If there is a value prop, and some segments were previously placeholders, mark them all as valid.
|
|
240
|
-
if (value && Object.keys(validSegments).length < Object.keys(allSegments).length) {
|
|
241
|
-
validSegments = {...allSegments};
|
|
242
|
-
setValidSegments(validSegments);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// If the value is set to null and all segments are valid, reset the placeholder.
|
|
246
|
-
if (value == null && Object.keys(validSegments).length === Object.keys(allSegments).length) {
|
|
247
|
-
validSegments = {};
|
|
248
|
-
setValidSegments(validSegments);
|
|
249
|
-
setPlaceholderDate(createPlaceholderDate(props.placeholderValue, granularity, calendar, defaultTimeZone));
|
|
220
|
+
let placeholder = useMemo(() => createPlaceholderDate(props.placeholderValue, granularity, calendar, defaultTimeZone), [props.placeholderValue, granularity, calendar, defaultTimeZone]);
|
|
221
|
+
let displaySegments = useMemo(() => {
|
|
222
|
+
let is12HourClock = hourCycle === 'h11' || hourCycle === 'h12';
|
|
223
|
+
let segments: SegmentType[] = ['era', 'year', 'month', 'day', 'hour', ...(is12HourClock ? ['dayPeriod' as const] : []), 'minute', 'second'];
|
|
224
|
+
let minIndex = segments.indexOf(props.maxGranularity || 'era');
|
|
225
|
+
let maxIndex = segments.indexOf(granularity === 'hour' && is12HourClock ? 'dayPeriod' : granularity);
|
|
226
|
+
return segments.slice(minIndex, maxIndex + 1);
|
|
227
|
+
}, [props.maxGranularity, granularity, hourCycle]);
|
|
228
|
+
|
|
229
|
+
let [lastValue, setLastValue] = useState(calendarValue);
|
|
230
|
+
let [lastCalendar, setLastCalendar] = useState(calendar);
|
|
231
|
+
let [lastHourCycle, setLastHourCycle] = useState(hourCycle);
|
|
232
|
+
if (calendarValue !== lastValue || hourCycle !== lastHourCycle || !isEqualCalendar(calendar, lastCalendar)) {
|
|
233
|
+
displayValue = new IncompleteDate(calendar, hourCycle, calendarValue);
|
|
234
|
+
setLastValue(calendarValue);
|
|
235
|
+
setLastCalendar(calendar);
|
|
236
|
+
setLastHourCycle(hourCycle);
|
|
237
|
+
setDisplayValue(displayValue);
|
|
250
238
|
}
|
|
251
239
|
|
|
252
|
-
|
|
253
|
-
let displayValue = calendarValue && Object.keys(validSegments).length >= Object.keys(allSegments).length ? calendarValue : placeholderDate;
|
|
254
|
-
let setValue = (newValue: DateValue) => {
|
|
240
|
+
let setValue = (newValue: DateValue | IncompleteDate | null) => {
|
|
255
241
|
if (props.isDisabled || props.isReadOnly) {
|
|
256
242
|
return;
|
|
257
243
|
}
|
|
258
|
-
let validKeys = Object.keys(validSegments);
|
|
259
|
-
let allKeys = Object.keys(allSegments);
|
|
260
244
|
|
|
261
|
-
|
|
262
|
-
|
|
245
|
+
if (newValue == null || (newValue instanceof IncompleteDate && newValue.isCleared(displaySegments))) {
|
|
246
|
+
setDisplayValue(new IncompleteDate(calendar, hourCycle, calendarValue));
|
|
263
247
|
setDate(null);
|
|
264
|
-
|
|
265
|
-
setValidSegments({});
|
|
266
|
-
} else if (
|
|
267
|
-
(validKeys.length === 0 && clearedSegment.current == null) ||
|
|
268
|
-
validKeys.length >= allKeys.length ||
|
|
269
|
-
(validKeys.length === allKeys.length - 1 && allSegments.dayPeriod && !validSegments.dayPeriod && clearedSegment.current !== 'dayPeriod')
|
|
270
|
-
) {
|
|
271
|
-
// If the field was empty (no valid segments) or all segments are completed, commit the new value.
|
|
272
|
-
// When committing from an empty state, mark every segment as valid so value is committed.
|
|
273
|
-
if (validKeys.length === 0) {
|
|
274
|
-
validSegments = {...allSegments};
|
|
275
|
-
setValidSegments(validSegments);
|
|
276
|
-
}
|
|
277
|
-
|
|
248
|
+
} else if (!(newValue instanceof IncompleteDate)) {
|
|
278
249
|
// The display calendar should not have any effect on the emitted value.
|
|
279
250
|
// Emit dates in the same calendar as the original value, if any, otherwise gregorian.
|
|
280
251
|
newValue = toCalendar(newValue, v?.calendar || new GregorianCalendar());
|
|
252
|
+
setDisplayValue(new IncompleteDate(calendar, hourCycle, calendarValue));
|
|
281
253
|
setDate(newValue);
|
|
282
254
|
} else {
|
|
283
|
-
|
|
255
|
+
// If the new value is complete and valid, trigger onChange eagerly.
|
|
256
|
+
// If it represents an incomplete or invalid value (e.g. February 30th),
|
|
257
|
+
// wait until the field is blurred to trigger onChange.
|
|
258
|
+
if (newValue.isComplete(displaySegments)) {
|
|
259
|
+
let dateValue = newValue.toValue(calendarValue ?? placeholder);
|
|
260
|
+
if (newValue.validate(dateValue, displaySegments)) {
|
|
261
|
+
let newDateValue = toCalendar(dateValue, v?.calendar || new GregorianCalendar());
|
|
262
|
+
if (!value || newDateValue.compare(value) !== 0) {
|
|
263
|
+
setDisplayValue(new IncompleteDate(calendar, hourCycle, calendarValue)); // reset in case prop isn't updated
|
|
264
|
+
setDate(newDateValue);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Incomplete/invalid value. Set temporary display override.
|
|
271
|
+
setDisplayValue(newValue);
|
|
284
272
|
}
|
|
285
|
-
clearedSegment.current = null;
|
|
286
273
|
};
|
|
287
274
|
|
|
288
|
-
let dateValue = useMemo(() =>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
// When the era field appears, mark it valid if the year field is already valid.
|
|
294
|
-
// If the era field disappears, remove it from the valid segments.
|
|
295
|
-
if (allSegments.era && validSegments.year && !validSegments.era) {
|
|
296
|
-
validSegments.era = true;
|
|
297
|
-
setValidSegments({...validSegments});
|
|
298
|
-
} else if (!allSegments.era && validSegments.era) {
|
|
299
|
-
delete validSegments.era;
|
|
300
|
-
setValidSegments({...validSegments});
|
|
301
|
-
}
|
|
275
|
+
let dateValue = useMemo(() => {
|
|
276
|
+
let v = displayValue.toValue(calendarValue ?? placeholder);
|
|
277
|
+
return v.toDate(timeZone);
|
|
278
|
+
}, [displayValue, timeZone, calendarValue, placeholder]);
|
|
302
279
|
|
|
303
|
-
let
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
setValidSegments({...validSegments});
|
|
309
|
-
};
|
|
280
|
+
let segments = useMemo(
|
|
281
|
+
() => processSegments(dateValue, displayValue, dateFormatter, resolvedOptions, calendar, locale, granularity),
|
|
282
|
+
[dateValue, dateFormatter, resolvedOptions, displayValue, calendar, locale, granularity]
|
|
283
|
+
);
|
|
310
284
|
|
|
311
|
-
let adjustSegment = (type:
|
|
312
|
-
|
|
313
|
-
markValid(type);
|
|
314
|
-
let validKeys = Object.keys(validSegments);
|
|
315
|
-
let allKeys = Object.keys(allSegments);
|
|
316
|
-
if (validKeys.length >= allKeys.length || (validKeys.length === allKeys.length - 1 && allSegments.dayPeriod && !validSegments.dayPeriod)) {
|
|
317
|
-
setValue(displayValue);
|
|
318
|
-
}
|
|
319
|
-
} else {
|
|
320
|
-
setValue(addSegment(displayValue, type, amount, resolvedOptions));
|
|
321
|
-
}
|
|
285
|
+
let adjustSegment = (type: SegmentType, amount: number) => {
|
|
286
|
+
setValue(displayValue.cycle(type, amount, placeholder, displaySegments));
|
|
322
287
|
};
|
|
323
288
|
|
|
324
289
|
let builtinValidation = useMemo(() => getValidationResult(
|
|
@@ -366,48 +331,43 @@ export function useDateFieldState<T extends DateValue = DateValue>(props: DateFi
|
|
|
366
331
|
decrementPage(part) {
|
|
367
332
|
adjustSegment(part, -(PAGE_STEP[part] || 1));
|
|
368
333
|
},
|
|
334
|
+
incrementToMax(part) {
|
|
335
|
+
let maxValue = part === 'hour' && hourCycle === 'h12'
|
|
336
|
+
? 11
|
|
337
|
+
: displayValue.getSegmentLimits(part)!.maxValue;
|
|
338
|
+
setValue(displayValue.set(part, maxValue, placeholder));
|
|
339
|
+
},
|
|
340
|
+
decrementToMin(part) {
|
|
341
|
+
let minValue = part === 'hour' && hourCycle === 'h12'
|
|
342
|
+
? 12
|
|
343
|
+
: displayValue.getSegmentLimits(part)!.minValue;
|
|
344
|
+
setValue(displayValue.set(part, minValue, placeholder));
|
|
345
|
+
},
|
|
369
346
|
setSegment(part, v: string | number) {
|
|
370
|
-
|
|
371
|
-
setValue(setSegment(displayValue, part, v, resolvedOptions));
|
|
347
|
+
setValue(displayValue.set(part, v, placeholder));
|
|
372
348
|
},
|
|
373
349
|
confirmPlaceholder() {
|
|
374
350
|
if (props.isDisabled || props.isReadOnly) {
|
|
375
351
|
return;
|
|
376
352
|
}
|
|
377
353
|
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
354
|
+
// If the display value is complete but invalid, we need to constrain it and emit onChange on blur.
|
|
355
|
+
if (displayValue.isComplete(displaySegments)) {
|
|
356
|
+
let dateValue = displayValue.toValue(calendarValue ?? placeholder);
|
|
357
|
+
let newDateValue = toCalendar(dateValue, v?.calendar || new GregorianCalendar());
|
|
358
|
+
if (!value || newDateValue.compare(value) !== 0) {
|
|
359
|
+
setDate(newDateValue);
|
|
360
|
+
}
|
|
361
|
+
setDisplayValue(new IncompleteDate(calendar, hourCycle, calendarValue));
|
|
385
362
|
}
|
|
386
363
|
},
|
|
387
364
|
clearSegment(part) {
|
|
388
|
-
delete validSegments[part];
|
|
389
|
-
clearedSegment.current = part;
|
|
390
|
-
setValidSegments({...validSegments});
|
|
391
|
-
|
|
392
|
-
let placeholder = createPlaceholderDate(props.placeholderValue, granularity, calendar, defaultTimeZone);
|
|
393
365
|
let value = displayValue;
|
|
394
366
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
let isPM = displayValue.hour >= 12;
|
|
398
|
-
let shouldBePM = placeholder.hour >= 12;
|
|
399
|
-
if (isPM && !shouldBePM) {
|
|
400
|
-
value = displayValue.set({hour: displayValue.hour - 12});
|
|
401
|
-
} else if (!isPM && shouldBePM) {
|
|
402
|
-
value = displayValue.set({hour: displayValue.hour + 12});
|
|
403
|
-
}
|
|
404
|
-
} else if (part === 'hour' && 'hour' in displayValue && displayValue.hour >= 12 && validSegments.dayPeriod) {
|
|
405
|
-
value = displayValue.set({hour: placeholder['hour'] + 12});
|
|
406
|
-
} else if (part in displayValue) {
|
|
407
|
-
value = displayValue.set({[part]: placeholder[part]});
|
|
367
|
+
if (part !== 'timeZoneName' && part !== 'literal') {
|
|
368
|
+
value = displayValue.clear(part);
|
|
408
369
|
}
|
|
409
370
|
|
|
410
|
-
setDate(null);
|
|
411
371
|
setValue(value);
|
|
412
372
|
},
|
|
413
373
|
formatValue(fieldOptions: FieldOptions) {
|
|
@@ -427,9 +387,34 @@ export function useDateFieldState<T extends DateValue = DateValue>(props: DateFi
|
|
|
427
387
|
};
|
|
428
388
|
}
|
|
429
389
|
|
|
430
|
-
function processSegments(
|
|
390
|
+
function processSegments(
|
|
391
|
+
dateValue: Date,
|
|
392
|
+
displayValue: IncompleteDate,
|
|
393
|
+
dateFormatter: Intl.DateTimeFormat,
|
|
394
|
+
resolvedOptions: Intl.ResolvedDateTimeFormatOptions,
|
|
395
|
+
calendar: Calendar,
|
|
396
|
+
locale: string,
|
|
397
|
+
granularity: Granularity
|
|
398
|
+
) : DateSegment[] {
|
|
431
399
|
let timeValue = ['hour', 'minute', 'second'];
|
|
432
400
|
let segments = dateFormatter.formatToParts(dateValue);
|
|
401
|
+
|
|
402
|
+
// In order to allow formatting temporarily invalid dates during editing (e.g. February 30th),
|
|
403
|
+
// use a NumberFormatter to manually format segments directly from raw numbers.
|
|
404
|
+
// When the user blurs the date field, the invalid segments will be constrained.
|
|
405
|
+
let numberFormatter = new NumberFormatter(locale, {useGrouping: false});
|
|
406
|
+
let twoDigitFormatter = new NumberFormatter(locale, {useGrouping: false, minimumIntegerDigits: 2});
|
|
407
|
+
for (let segment of segments) {
|
|
408
|
+
if (segment.type === 'year' || segment.type === 'month' || segment.type === 'day' || segment.type === 'hour') {
|
|
409
|
+
let value = displayValue[segment.type] ?? 0;
|
|
410
|
+
if (resolvedOptions[segment.type] === '2-digit') {
|
|
411
|
+
segment.value = twoDigitFormatter.format(value);
|
|
412
|
+
} else {
|
|
413
|
+
segment.value = numberFormatter.format(value);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
433
418
|
let processedSegments: DateSegment[] = [];
|
|
434
419
|
for (let segment of segments) {
|
|
435
420
|
let type = TYPE_MAPPING[segment.type] || segment.type;
|
|
@@ -438,13 +423,13 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption
|
|
|
438
423
|
isEditable = false;
|
|
439
424
|
}
|
|
440
425
|
|
|
441
|
-
let isPlaceholder = EDITABLE_SEGMENTS[type] &&
|
|
426
|
+
let isPlaceholder = EDITABLE_SEGMENTS[type] && displayValue[segment.type] == null;
|
|
442
427
|
let placeholder = EDITABLE_SEGMENTS[type] ? getPlaceholder(type, segment.value, locale) : null;
|
|
443
428
|
|
|
444
429
|
let dateSegment = {
|
|
445
430
|
type,
|
|
446
431
|
text: isPlaceholder ? placeholder : segment.value,
|
|
447
|
-
...getSegmentLimits(
|
|
432
|
+
...displayValue.getSegmentLimits(type),
|
|
448
433
|
isPlaceholder,
|
|
449
434
|
placeholder,
|
|
450
435
|
isEditable
|
|
@@ -458,7 +443,6 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption
|
|
|
458
443
|
processedSegments.push({
|
|
459
444
|
type: 'literal',
|
|
460
445
|
text: '\u2066',
|
|
461
|
-
...getSegmentLimits(displayValue, 'literal', resolvedOptions),
|
|
462
446
|
isPlaceholder: false,
|
|
463
447
|
placeholder: '',
|
|
464
448
|
isEditable: false
|
|
@@ -469,7 +453,6 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption
|
|
|
469
453
|
processedSegments.push({
|
|
470
454
|
type: 'literal',
|
|
471
455
|
text: '\u2069',
|
|
472
|
-
...getSegmentLimits(displayValue, 'literal', resolvedOptions),
|
|
473
456
|
isPlaceholder: false,
|
|
474
457
|
placeholder: '',
|
|
475
458
|
isEditable: false
|
|
@@ -481,7 +464,6 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption
|
|
|
481
464
|
processedSegments.push({
|
|
482
465
|
type: 'literal',
|
|
483
466
|
text: '\u2069',
|
|
484
|
-
...getSegmentLimits(displayValue, 'literal', resolvedOptions),
|
|
485
467
|
isPlaceholder: false,
|
|
486
468
|
placeholder: '',
|
|
487
469
|
isEditable: false
|
|
@@ -494,145 +476,3 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption
|
|
|
494
476
|
|
|
495
477
|
return processedSegments;
|
|
496
478
|
}
|
|
497
|
-
|
|
498
|
-
function getSegmentLimits(date: DateValue, type: string, options: Intl.ResolvedDateTimeFormatOptions) {
|
|
499
|
-
switch (type) {
|
|
500
|
-
case 'era': {
|
|
501
|
-
let eras = date.calendar.getEras();
|
|
502
|
-
return {
|
|
503
|
-
value: eras.indexOf(date.era),
|
|
504
|
-
minValue: 0,
|
|
505
|
-
maxValue: eras.length - 1
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
case 'year':
|
|
509
|
-
return {
|
|
510
|
-
value: date.year,
|
|
511
|
-
minValue: 1,
|
|
512
|
-
maxValue: date.calendar.getYearsInEra(date)
|
|
513
|
-
};
|
|
514
|
-
case 'month':
|
|
515
|
-
return {
|
|
516
|
-
value: date.month,
|
|
517
|
-
minValue: getMinimumMonthInYear(date),
|
|
518
|
-
maxValue: date.calendar.getMonthsInYear(date)
|
|
519
|
-
};
|
|
520
|
-
case 'day':
|
|
521
|
-
return {
|
|
522
|
-
value: date.day,
|
|
523
|
-
minValue: getMinimumDayInMonth(date),
|
|
524
|
-
maxValue: date.calendar.getDaysInMonth(date)
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if ('hour' in date) {
|
|
529
|
-
switch (type) {
|
|
530
|
-
case 'dayPeriod':
|
|
531
|
-
return {
|
|
532
|
-
value: date.hour >= 12 ? 12 : 0,
|
|
533
|
-
minValue: 0,
|
|
534
|
-
maxValue: 12
|
|
535
|
-
};
|
|
536
|
-
case 'hour':
|
|
537
|
-
if (options.hour12) {
|
|
538
|
-
let isPM = date.hour >= 12;
|
|
539
|
-
return {
|
|
540
|
-
value: date.hour,
|
|
541
|
-
minValue: isPM ? 12 : 0,
|
|
542
|
-
maxValue: isPM ? 23 : 11
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
return {
|
|
547
|
-
value: date.hour,
|
|
548
|
-
minValue: 0,
|
|
549
|
-
maxValue: 23
|
|
550
|
-
};
|
|
551
|
-
case 'minute':
|
|
552
|
-
return {
|
|
553
|
-
value: date.minute,
|
|
554
|
-
minValue: 0,
|
|
555
|
-
maxValue: 59
|
|
556
|
-
};
|
|
557
|
-
case 'second':
|
|
558
|
-
return {
|
|
559
|
-
value: date.second,
|
|
560
|
-
minValue: 0,
|
|
561
|
-
maxValue: 59
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
return {};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function addSegment(value: DateValue, part: string, amount: number, options: Intl.ResolvedDateTimeFormatOptions) {
|
|
570
|
-
switch (part) {
|
|
571
|
-
case 'era':
|
|
572
|
-
case 'year':
|
|
573
|
-
case 'month':
|
|
574
|
-
case 'day':
|
|
575
|
-
return value.cycle(part, amount, {round: part === 'year'});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
if ('hour' in value) {
|
|
579
|
-
switch (part) {
|
|
580
|
-
case 'dayPeriod': {
|
|
581
|
-
let hours = value.hour;
|
|
582
|
-
let isPM = hours >= 12;
|
|
583
|
-
return value.set({hour: isPM ? hours - 12 : hours + 12});
|
|
584
|
-
}
|
|
585
|
-
case 'hour':
|
|
586
|
-
case 'minute':
|
|
587
|
-
case 'second':
|
|
588
|
-
return value.cycle(part, amount, {
|
|
589
|
-
round: part !== 'hour',
|
|
590
|
-
hourCycle: options.hour12 ? 12 : 24
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
throw new Error('Unknown segment: ' + part);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function setSegment(value: DateValue, part: string, segmentValue: number | string, options: Intl.ResolvedDateTimeFormatOptions) {
|
|
599
|
-
switch (part) {
|
|
600
|
-
case 'day':
|
|
601
|
-
case 'month':
|
|
602
|
-
case 'year':
|
|
603
|
-
case 'era':
|
|
604
|
-
return value.set({[part]: segmentValue});
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
if ('hour' in value && typeof segmentValue === 'number') {
|
|
608
|
-
switch (part) {
|
|
609
|
-
case 'dayPeriod': {
|
|
610
|
-
let hours = value.hour;
|
|
611
|
-
let wasPM = hours >= 12;
|
|
612
|
-
let isPM = segmentValue >= 12;
|
|
613
|
-
if (isPM === wasPM) {
|
|
614
|
-
return value;
|
|
615
|
-
}
|
|
616
|
-
return value.set({hour: wasPM ? hours - 12 : hours + 12});
|
|
617
|
-
}
|
|
618
|
-
case 'hour':
|
|
619
|
-
// In 12 hour time, ensure that AM/PM does not change
|
|
620
|
-
if (options.hour12) {
|
|
621
|
-
let hours = value.hour;
|
|
622
|
-
let wasPM = hours >= 12;
|
|
623
|
-
if (!wasPM && segmentValue === 12) {
|
|
624
|
-
segmentValue = 0;
|
|
625
|
-
}
|
|
626
|
-
if (wasPM && segmentValue < 12) {
|
|
627
|
-
segmentValue += 12;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
// fallthrough
|
|
631
|
-
case 'minute':
|
|
632
|
-
case 'second':
|
|
633
|
-
return value.set({[part]: segmentValue});
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
throw new Error('Unknown segment: ' + part);
|
|
638
|
-
}
|