1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | import classNames from "classnames";
|
26 | import * as React from "react";
|
27 | import type { DayPickerProps } from "react-day-picker";
|
28 |
|
29 | import {
|
30 | type ButtonProps,
|
31 | DISPLAYNAME_PREFIX,
|
32 | InputGroup,
|
33 | type InputGroupProps,
|
34 | mergeRefs,
|
35 | Popover,
|
36 | type PopoverClickTargetHandlers,
|
37 | type PopoverTargetProps,
|
38 | type Props,
|
39 | Tag,
|
40 | Utils,
|
41 | } from "@blueprintjs/core";
|
42 |
|
43 | import { Classes, type DateFormatProps, type DatePickerBaseProps } from "../../common";
|
44 | import { getFormattedDateString } from "../../common/dateFormatProps";
|
45 | import type { DatetimePopoverProps } from "../../common/datetimePopoverProps";
|
46 | import { hasMonthChanged, hasTimeChanged, isDateValid, isDayInRange } from "../../common/dateUtils";
|
47 | import * as Errors from "../../common/errors";
|
48 | import { getCurrentTimezone } from "../../common/getTimezone";
|
49 | import { UTC_TIME } from "../../common/timezoneItems";
|
50 | import { getTimezoneShortName, isValidTimezone } from "../../common/timezoneNameUtils";
|
51 | import {
|
52 | convertLocalDateToTimezoneTime,
|
53 | getDateObjectFromIsoString,
|
54 | getIsoEquivalentWithUpdatedTimezone,
|
55 | } from "../../common/timezoneUtils";
|
56 | import { DatePicker } from "../date-picker/datePicker";
|
57 | import { DatePickerUtils } from "../date-picker/datePickerUtils";
|
58 | import type { DatePickerShortcut } from "../shortcuts/shortcuts";
|
59 | import { TimezoneSelect } from "../timezone-select/timezoneSelect";
|
60 |
|
61 | export interface DateInputProps extends DatePickerBaseProps, DateFormatProps, DatetimePopoverProps, Props {
|
62 | |
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 | canClearSelection?: boolean;
|
69 |
|
70 | |
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | clearButtonText?: string;
|
77 |
|
78 | |
79 |
|
80 |
|
81 |
|
82 |
|
83 | closeOnSelection?: boolean;
|
84 |
|
85 | |
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | defaultTimezone?: string;
|
93 |
|
94 | |
95 |
|
96 |
|
97 |
|
98 |
|
99 | disableTimezoneSelect?: boolean;
|
100 |
|
101 | |
102 |
|
103 |
|
104 | defaultValue?: string;
|
105 |
|
106 | |
107 |
|
108 |
|
109 |
|
110 |
|
111 | disabled?: boolean;
|
112 |
|
113 | |
114 |
|
115 |
|
116 | fill?: boolean;
|
117 |
|
118 | |
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | inputProps?: Partial<Omit<InputGroupProps, "disabled" | "type" | "value">>;
|
129 |
|
130 | |
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | onChange?: (newDate: string | null, isUserChange: boolean) => void;
|
138 |
|
139 | |
140 |
|
141 |
|
142 |
|
143 |
|
144 | onError?: (errorDate: Date) => void;
|
145 |
|
146 | |
147 |
|
148 |
|
149 |
|
150 |
|
151 | onTimezoneChange?: (timezone: string) => void;
|
152 |
|
153 | |
154 |
|
155 |
|
156 | rightElement?: React.JSX.Element;
|
157 |
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 | showActionsBar?: boolean;
|
164 |
|
165 | |
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 | showTimezoneSelect?: boolean;
|
172 |
|
173 | |
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 | shortcuts?: boolean | DatePickerShortcut[];
|
182 |
|
183 | |
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 | timezone?: string;
|
194 |
|
195 | |
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 | todayButtonText?: string;
|
202 |
|
203 |
|
204 | value?: string | null;
|
205 | }
|
206 |
|
207 | const timezoneSelectButtonProps: Partial<ButtonProps> = {
|
208 | fill: false,
|
209 | minimal: true,
|
210 | outlined: true,
|
211 | };
|
212 |
|
213 | const INVALID_DATE = new Date(undefined!);
|
214 | const DEFAULT_MAX_DATE = DatePickerUtils.getDefaultMaxDate();
|
215 | const DEFAULT_MIN_DATE = DatePickerUtils.getDefaultMinDate();
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | export const DateInput: React.FC<DateInputProps> = React.memo(function _DateInput(props) {
|
224 | const {
|
225 | defaultTimezone,
|
226 | defaultValue,
|
227 | disableTimezoneSelect,
|
228 | fill,
|
229 | inputProps = {},
|
230 |
|
231 | maxDate = DEFAULT_MAX_DATE,
|
232 | minDate = DEFAULT_MIN_DATE,
|
233 | placeholder,
|
234 | popoverProps = {},
|
235 | popoverRef,
|
236 | showTimezoneSelect,
|
237 | timePrecision,
|
238 | timezone,
|
239 | value,
|
240 | ...datePickerProps
|
241 | } = props;
|
242 |
|
243 |
|
244 |
|
245 |
|
246 | const inputRef = React.useRef<HTMLInputElement | null>(null);
|
247 | const popoverContentRef = React.useRef<HTMLDivElement | null>(null);
|
248 | const popoverId = Utils.uniqueId("date-picker");
|
249 |
|
250 |
|
251 |
|
252 |
|
253 | const [isOpen, setIsOpen] = React.useState(false);
|
254 | const [timezoneValue, setTimezoneValue] = React.useState(getInitialTimezoneValue(props));
|
255 | const valueFromProps = React.useMemo(
|
256 | () => getDateObjectFromIsoString(value, timezoneValue),
|
257 | [timezoneValue, value],
|
258 | );
|
259 | const isControlled = valueFromProps !== undefined;
|
260 | const defaultValueFromProps = React.useMemo(
|
261 | () => getDateObjectFromIsoString(defaultValue, timezoneValue),
|
262 | [defaultValue, defaultTimezone],
|
263 | );
|
264 | const [valueAsDate, setValue] = React.useState<Date | null>(isControlled ? valueFromProps : defaultValueFromProps!);
|
265 |
|
266 | const [selectedShortcutIndex, setSelectedShortcutIndex] = React.useState<number | undefined>(undefined);
|
267 | const [isInputFocused, setIsInputFocused] = React.useState(false);
|
268 |
|
269 |
|
270 | const formattedDateString = React.useMemo(() => {
|
271 | return valueAsDate === null ? undefined : getFormattedDateString(valueAsDate, props);
|
272 | }, [
|
273 | valueAsDate,
|
274 | minDate,
|
275 | maxDate,
|
276 |
|
277 |
|
278 | props.formatDate,
|
279 | props.locale,
|
280 | props.invalidDateMessage,
|
281 | props.outOfRangeMessage,
|
282 | ]);
|
283 | const [inputValue, setInputValue] = React.useState(formattedDateString ?? undefined);
|
284 |
|
285 | const isErrorState =
|
286 | valueAsDate != null && (!isDateValid(valueAsDate) || !isDayInRange(valueAsDate, [minDate, maxDate]));
|
287 |
|
288 |
|
289 |
|
290 |
|
291 | React.useEffect(() => {
|
292 | if (isControlled) {
|
293 | setValue(valueFromProps);
|
294 | }
|
295 | }, [valueFromProps]);
|
296 |
|
297 | React.useEffect(() => {
|
298 |
|
299 | if (defaultTimezone !== undefined && isValidTimezone(defaultTimezone)) {
|
300 | setTimezoneValue(defaultTimezone);
|
301 | }
|
302 | }, [defaultTimezone]);
|
303 |
|
304 | React.useEffect(() => {
|
305 |
|
306 | if (timezone !== undefined && isValidTimezone(timezone)) {
|
307 | setTimezoneValue(timezone);
|
308 | }
|
309 | }, [timezone]);
|
310 |
|
311 | React.useEffect(() => {
|
312 | if (isControlled && !isInputFocused) {
|
313 | setInputValue(formattedDateString);
|
314 | }
|
315 | }, [formattedDateString]);
|
316 |
|
317 |
|
318 |
|
319 |
|
320 | const handlePopoverClose = React.useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
321 | popoverProps.onClose?.(e);
|
322 | setIsOpen(false);
|
323 | }, []);
|
324 |
|
325 | const handleDateChange = React.useCallback(
|
326 | (newDate: Date | null, isUserChange: boolean, didSubmitWithEnter = false) => {
|
327 | const prevDate = valueAsDate;
|
328 |
|
329 | if (newDate === null) {
|
330 | if (!isControlled && !didSubmitWithEnter) {
|
331 |
|
332 | setInputValue("");
|
333 | }
|
334 | props.onChange?.(null, isUserChange);
|
335 | return;
|
336 | }
|
337 |
|
338 |
|
339 |
|
340 |
|
341 | const newIsOpen =
|
342 | !isUserChange ||
|
343 | !props.closeOnSelection ||
|
344 | (prevDate != null &&
|
345 | (hasMonthChanged(prevDate, newDate) ||
|
346 | (timePrecision !== undefined && hasTimeChanged(prevDate, newDate))));
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 | const newIsInputFocused = didSubmitWithEnter ? true : false;
|
354 |
|
355 | if (isControlled) {
|
356 | setIsInputFocused(newIsInputFocused);
|
357 | setIsOpen(newIsOpen);
|
358 | } else {
|
359 | const newFormattedDateString = getFormattedDateString(newDate, props);
|
360 | setIsInputFocused(newIsInputFocused);
|
361 | setIsOpen(newIsOpen);
|
362 | setValue(newDate);
|
363 | setInputValue(newFormattedDateString);
|
364 | }
|
365 |
|
366 | const newIsoDateString = getIsoEquivalentWithUpdatedTimezone(newDate, timezoneValue, timePrecision);
|
367 | props.onChange?.(newIsoDateString, isUserChange);
|
368 | },
|
369 | [props.onChange, timezoneValue, timePrecision, valueAsDate],
|
370 | );
|
371 |
|
372 | const dayPickerProps: DayPickerProps = {
|
373 | ...props.dayPickerProps,
|
374 | onDayKeyDown: (day, modifiers, e) => {
|
375 | props.dayPickerProps?.onDayKeyDown?.(day, modifiers, e);
|
376 | },
|
377 | onMonthChange: (month: Date) => {
|
378 | props.dayPickerProps?.onMonthChange?.(month);
|
379 | },
|
380 | };
|
381 |
|
382 | const handleShortcutChange = React.useCallback((_: DatePickerShortcut, index: number) => {
|
383 | setSelectedShortcutIndex(index);
|
384 | }, []);
|
385 |
|
386 | const handleStartFocusBoundaryFocusIn = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
|
387 | if (popoverContentRef.current?.contains(getRelatedTargetWithFallback(e))) {
|
388 |
|
389 |
|
390 | inputRef.current?.focus();
|
391 | } else {
|
392 | getKeyboardFocusableElements(popoverContentRef).shift()?.focus();
|
393 | }
|
394 | }, []);
|
395 |
|
396 | const handleEndFocusBoundaryFocusIn = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
|
397 | if (popoverContentRef.current?.contains(getRelatedTargetWithFallback(e))) {
|
398 | inputRef.current?.focus();
|
399 | handlePopoverClose(e);
|
400 | } else {
|
401 | getKeyboardFocusableElements(popoverContentRef).pop()?.focus();
|
402 | }
|
403 | }, []);
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | const popoverContent = (
|
409 | <div ref={popoverContentRef} role="dialog" aria-label="date picker" id={popoverId}>
|
410 | <div onFocus={handleStartFocusBoundaryFocusIn} tabIndex={0} />
|
411 | <DatePicker
|
412 | {...datePickerProps}
|
413 | dayPickerProps={dayPickerProps}
|
414 | maxDate={maxDate}
|
415 | minDate={minDate}
|
416 | onChange={handleDateChange}
|
417 | onShortcutChange={handleShortcutChange}
|
418 | selectedShortcutIndex={selectedShortcutIndex}
|
419 | timePrecision={timePrecision}
|
420 |
|
421 |
|
422 | value={isErrorState ? null : valueAsDate}
|
423 | />
|
424 | <div onFocus={handleEndFocusBoundaryFocusIn} tabIndex={0} />
|
425 | </div>
|
426 | );
|
427 |
|
428 |
|
429 |
|
430 |
|
431 |
|
432 |
|
433 | const tzSelectDate = React.useMemo(
|
434 | () =>
|
435 | valueAsDate != null && isDateValid(valueAsDate)
|
436 | ? valueAsDate
|
437 | : convertLocalDateToTimezoneTime(new Date(), timezoneValue),
|
438 | [timezoneValue, valueAsDate],
|
439 | );
|
440 |
|
441 | const isTimezoneSelectHidden = timePrecision === undefined || showTimezoneSelect === false;
|
442 | const isTimezoneSelectDisabled = props.disabled || disableTimezoneSelect;
|
443 |
|
444 | const handleTimezoneChange = React.useCallback(
|
445 | (newTimezone: string) => {
|
446 | if (timezone === undefined) {
|
447 |
|
448 | setTimezoneValue(newTimezone);
|
449 | }
|
450 | props.onTimezoneChange?.(newTimezone);
|
451 |
|
452 | if (valueAsDate != null) {
|
453 | const newDateString = getIsoEquivalentWithUpdatedTimezone(valueAsDate, newTimezone, timePrecision);
|
454 | props.onChange?.(newDateString, true);
|
455 | }
|
456 | },
|
457 | [props.onChange, valueAsDate, timePrecision],
|
458 | );
|
459 |
|
460 | const maybeTimezonePicker = isTimezoneSelectHidden ? undefined : (
|
461 | <TimezoneSelect
|
462 | buttonProps={timezoneSelectButtonProps}
|
463 | className={Classes.DATE_INPUT_TIMEZONE_SELECT}
|
464 | date={tzSelectDate}
|
465 | disabled={isTimezoneSelectDisabled}
|
466 | onChange={handleTimezoneChange}
|
467 | value={timezoneValue}
|
468 | >
|
469 | <Tag
|
470 | interactive={!isTimezoneSelectDisabled}
|
471 | minimal={true}
|
472 | rightIcon={isTimezoneSelectDisabled ? undefined : "caret-down"}
|
473 | >
|
474 | {getTimezoneShortName(timezoneValue, tzSelectDate)}
|
475 | </Tag>
|
476 | </TimezoneSelect>
|
477 | );
|
478 |
|
479 |
|
480 |
|
481 |
|
482 | const parseDate = React.useCallback(
|
483 | (dateString: string) => {
|
484 | if (dateString === props.outOfRangeMessage || dateString === props.invalidDateMessage) {
|
485 | return null;
|
486 | }
|
487 | const newDate = props.parseDate(dateString, props.locale);
|
488 | return newDate === false ? INVALID_DATE : newDate;
|
489 | },
|
490 |
|
491 |
|
492 | [props.outOfRangeMessage, props.invalidDateMessage, props.parseDate, props.locale],
|
493 | );
|
494 |
|
495 | const handleInputFocus = React.useCallback(
|
496 | (e: React.FocusEvent<HTMLInputElement>) => {
|
497 | setIsInputFocused(true);
|
498 | setIsOpen(true);
|
499 | setInputValue(formattedDateString);
|
500 | props.inputProps?.onFocus?.(e);
|
501 | },
|
502 | [formattedDateString, props.inputProps?.onFocus],
|
503 | );
|
504 |
|
505 | const handleInputBlur = React.useCallback(
|
506 | (e: React.FocusEvent<HTMLInputElement>) => {
|
507 | if (inputValue == null || valueAsDate == null) {
|
508 | return;
|
509 | }
|
510 |
|
511 | const date = parseDate(inputValue);
|
512 |
|
513 | if (
|
514 | inputValue.length > 0 &&
|
515 | inputValue !== formattedDateString &&
|
516 | (!isDateValid(date) || !isDayInRange(date, [minDate, maxDate]))
|
517 | ) {
|
518 | if (isControlled) {
|
519 | setIsInputFocused(false);
|
520 | } else {
|
521 | setIsInputFocused(false);
|
522 | setValue(date);
|
523 | setInputValue(undefined);
|
524 | }
|
525 |
|
526 | if (date === null) {
|
527 | props.onChange?.(null, true);
|
528 | } else {
|
529 | props.onError?.(date);
|
530 | }
|
531 | } else {
|
532 | if (inputValue.length === 0) {
|
533 | setIsInputFocused(false);
|
534 | setValue(null);
|
535 | setInputValue(undefined);
|
536 | } else {
|
537 | setIsInputFocused(false);
|
538 | }
|
539 | }
|
540 | props.inputProps?.onBlur?.(e);
|
541 | },
|
542 | [
|
543 | parseDate,
|
544 | formattedDateString,
|
545 | inputValue,
|
546 | valueAsDate,
|
547 | minDate,
|
548 | maxDate,
|
549 | props.onChange,
|
550 | props.onError,
|
551 | props.inputProps?.onBlur,
|
552 | ],
|
553 | );
|
554 |
|
555 | const handleInputChange = React.useCallback(
|
556 | (e: React.ChangeEvent<HTMLInputElement>) => {
|
557 | const valueString = (e.target as HTMLInputElement).value;
|
558 | const inputValueAsDate = parseDate(valueString);
|
559 |
|
560 | if (isDateValid(inputValueAsDate) && isDayInRange(inputValueAsDate, [minDate, maxDate])) {
|
561 | if (isControlled) {
|
562 | setInputValue(valueString);
|
563 | } else {
|
564 | setValue(inputValueAsDate);
|
565 | setInputValue(valueString);
|
566 | }
|
567 | const newIsoDateString = getIsoEquivalentWithUpdatedTimezone(
|
568 | inputValueAsDate,
|
569 | timezoneValue,
|
570 | timePrecision,
|
571 | );
|
572 | props.onChange?.(newIsoDateString, true);
|
573 | } else {
|
574 | if (valueString.length === 0) {
|
575 | props.onChange?.(null, true);
|
576 | }
|
577 | setValue(inputValueAsDate);
|
578 | setInputValue(valueString);
|
579 | }
|
580 | props.inputProps?.onChange?.(e);
|
581 | },
|
582 | [minDate, maxDate, timezoneValue, timePrecision, parseDate, props.onChange, props.inputProps?.onChange],
|
583 | );
|
584 |
|
585 | const handleInputClick = React.useCallback(
|
586 | (e: React.MouseEvent<HTMLInputElement>) => {
|
587 |
|
588 |
|
589 | e.stopPropagation();
|
590 | props.inputProps?.onClick?.(e);
|
591 | },
|
592 | [props.inputProps?.onClick],
|
593 | );
|
594 |
|
595 | const handleInputKeyDown = React.useCallback(
|
596 | (e: React.KeyboardEvent<HTMLInputElement>) => {
|
597 | if (e.key === "Tab" && e.shiftKey) {
|
598 |
|
599 | handlePopoverClose(e);
|
600 | } else if (e.key === "Tab" && isOpen) {
|
601 | getKeyboardFocusableElements(popoverContentRef).shift()?.focus();
|
602 |
|
603 | e.preventDefault();
|
604 | } else if (e.key === "Escape") {
|
605 | setIsOpen(false);
|
606 | inputRef.current?.blur();
|
607 | } else if (e.key === "Enter" && inputValue != null) {
|
608 | const nextDate = parseDate(inputValue);
|
609 | if (isDateValid(nextDate)) {
|
610 | handleDateChange(nextDate, true, true);
|
611 | }
|
612 | }
|
613 |
|
614 | props.inputProps?.onKeyDown?.(e);
|
615 | },
|
616 | [inputValue, parseDate, props.inputProps?.onKeyDown],
|
617 | );
|
618 |
|
619 |
|
620 |
|
621 |
|
622 | const shouldShowErrorStyling =
|
623 | !isInputFocused || inputValue === props.outOfRangeMessage || inputValue === props.invalidDateMessage;
|
624 |
|
625 |
|
626 | const renderTarget = React.useCallback(
|
627 | ({ isOpen: targetIsOpen, ref, ...targetProps }: PopoverTargetProps & PopoverClickTargetHandlers) => {
|
628 | return (
|
629 | <InputGroup
|
630 | autoComplete="off"
|
631 | className={classNames(targetProps.className, inputProps.className)}
|
632 | intent={shouldShowErrorStyling && isErrorState ? "danger" : "none"}
|
633 | placeholder={placeholder}
|
634 | rightElement={
|
635 | <>
|
636 | {props.rightElement}
|
637 | {maybeTimezonePicker}
|
638 | </>
|
639 | }
|
640 | tagName={popoverProps.targetTagName}
|
641 | type="text"
|
642 | role="combobox"
|
643 | {...targetProps}
|
644 | {...inputProps}
|
645 | aria-controls={popoverId}
|
646 | aria-expanded={targetIsOpen}
|
647 | disabled={props.disabled}
|
648 | fill={fill}
|
649 | inputRef={mergeRefs(ref, inputRef, props.inputProps?.inputRef ?? null)}
|
650 | onBlur={handleInputBlur}
|
651 | onChange={handleInputChange}
|
652 | onClick={handleInputClick}
|
653 | onFocus={handleInputFocus}
|
654 | onKeyDown={handleInputKeyDown}
|
655 | value={(isInputFocused ? inputValue : formattedDateString) ?? ""}
|
656 | />
|
657 | );
|
658 | },
|
659 | [
|
660 | fill,
|
661 | formattedDateString,
|
662 | inputValue,
|
663 | isInputFocused,
|
664 | isTimezoneSelectDisabled,
|
665 | isTimezoneSelectHidden,
|
666 | placeholder,
|
667 | shouldShowErrorStyling,
|
668 | timezoneValue,
|
669 | props.disabled,
|
670 | props.inputProps,
|
671 | props.rightElement,
|
672 | ],
|
673 | );
|
674 |
|
675 |
|
676 | return (
|
677 | <Popover
|
678 | isOpen={isOpen && !props.disabled}
|
679 | {...popoverProps}
|
680 | autoFocus={false}
|
681 | className={classNames(Classes.DATE_INPUT, popoverProps.className, props.className)}
|
682 | content={popoverContent}
|
683 | enforceFocus={false}
|
684 | onClose={handlePopoverClose}
|
685 | popoverClassName={classNames(Classes.DATE_INPUT_POPOVER, popoverProps.popoverClassName)}
|
686 | ref={popoverRef}
|
687 | renderTarget={renderTarget}
|
688 | />
|
689 | );
|
690 | });
|
691 | DateInput.displayName = `${DISPLAYNAME_PREFIX}.DateInput`;
|
692 | DateInput.defaultProps = {
|
693 | closeOnSelection: true,
|
694 | disabled: false,
|
695 | invalidDateMessage: "Invalid date",
|
696 | maxDate: DEFAULT_MAX_DATE,
|
697 | minDate: DEFAULT_MIN_DATE,
|
698 | outOfRangeMessage: "Out of range",
|
699 | reverseMonthAndYearMenus: false,
|
700 | };
|
701 |
|
702 | function getInitialTimezoneValue({ defaultTimezone, timezone }: DateInputProps) {
|
703 | if (timezone !== undefined) {
|
704 |
|
705 | if (isValidTimezone(timezone)) {
|
706 | return timezone;
|
707 | } else {
|
708 | console.error(Errors.DATEINPUT_INVALID_TIMEZONE);
|
709 | return UTC_TIME.ianaCode;
|
710 | }
|
711 | } else if (defaultTimezone !== undefined) {
|
712 |
|
713 | if (isValidTimezone(defaultTimezone)) {
|
714 | return defaultTimezone;
|
715 | } else {
|
716 | console.error(Errors.DATEINPUT_INVALID_DEFAULT_TIMEZONE);
|
717 | return UTC_TIME.ianaCode;
|
718 | }
|
719 | } else {
|
720 |
|
721 | return getCurrentTimezone();
|
722 | }
|
723 | }
|
724 |
|
725 | function getRelatedTargetWithFallback(e: React.FocusEvent<HTMLElement>) {
|
726 | return (e.relatedTarget ?? Utils.getActiveElement(e.currentTarget)) as HTMLElement;
|
727 | }
|
728 |
|
729 | function getKeyboardFocusableElements(popoverContentRef: React.MutableRefObject<HTMLDivElement | null>) {
|
730 | if (popoverContentRef.current === null) {
|
731 | return [];
|
732 | }
|
733 |
|
734 | const elements: HTMLElement[] = Array.from(
|
735 | popoverContentRef.current.querySelectorAll("button:not([disabled]),input,[tabindex]:not([tabindex='-1'])"),
|
736 | );
|
737 |
|
738 | elements.pop();
|
739 | elements.shift();
|
740 | return elements;
|
741 | }
|