/**
* WordPress dependencies
*/
import {
BaseControl,
privateApis as componentsPrivateApis,
} from '@wordpress/components';
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { dateI18n, getDate, getSettings } from '@wordpress/date';
import { Stack } from '@wordpress/ui';
/**
* Internal dependencies
*/
import type { DataFormControlProps, FormatDatetime } from '../../types';
import { OPERATOR_IN_THE_PAST, OPERATOR_OVER } from '../../constants';
import RelativeDateControl from './utils/relative-date-control';
import getCustomValidity from './utils/get-custom-validity';
import parseDateTime from '../../field-types/utils/parse-date-time';
import { unlock } from '../../lock-unlock';
const { DateCalendar, ValidatedInputControl } = unlock( componentsPrivateApis );
const formatDateTime = ( value?: string ): string => {
if ( ! value ) {
return '';
}
// Format in WordPress timezone for datetime-local input: YYYY-MM-DDTHH:mm
return dateI18n( 'Y-m-d\\TH:i', getDate( value ) );
};
function CalendarDateTimeControl< Item >( {
data,
field,
onChange,
hideLabelFromVision,
markWhenOptional,
validity,
config,
}: DataFormControlProps< Item > ) {
const { compact } = config || {};
const { id, label, description, setValue, getValue, isValid } = field;
const fieldValue = getValue( { item: data } );
const value = typeof fieldValue === 'string' ? fieldValue : undefined;
const [ calendarMonth, setCalendarMonth ] = useState< Date >( () => {
const parsedDate = parseDateTime( value );
return parsedDate || new Date(); // Default to current month
} );
const inputControlRef = useRef< HTMLInputElement >( null );
const validationTimeoutRef =
useRef< ReturnType< typeof setTimeout > >( undefined );
const previousFocusRef = useRef< Element | null >( null );
const onChangeCallback = useCallback(
( newValue: string | undefined ) =>
onChange( setValue( { item: data, value: newValue } ) ),
[ data, onChange, setValue ]
);
// Cleanup timeout on unmount
useEffect( () => {
return () => {
if ( validationTimeoutRef.current ) {
clearTimeout( validationTimeoutRef.current );
}
};
}, [] );
const onSelectDate = useCallback(
( newDate: Date | undefined | null ) => {
let dateTimeValue: string | undefined;
if ( newDate ) {
// Extract the date part in WP timezone from the calendar selection
const wpDate = dateI18n( 'Y-m-d', newDate );
// Preserve time if it exists in current value, otherwise use current time
let wpTime: string;
if ( value ) {
wpTime = dateI18n( 'H:i', getDate( value ) );
} else {
wpTime = dateI18n( 'H:i', newDate );
}
// Combine date and time in WP timezone and convert to ISO
const finalDateTime = getDate( `${ wpDate }T${ wpTime }` );
dateTimeValue = finalDateTime.toISOString();
onChangeCallback( dateTimeValue );
// Clear any existing timeout
if ( validationTimeoutRef.current ) {
clearTimeout( validationTimeoutRef.current );
}
} else {
onChangeCallback( undefined );
}
// Save the currently focused element
previousFocusRef.current =
inputControlRef.current &&
inputControlRef.current.ownerDocument.activeElement;
// Trigger validation display by simulating focus, blur, and changes.
// Use a timeout to ensure it runs after the value update.
validationTimeoutRef.current = setTimeout( () => {
if ( inputControlRef.current ) {
inputControlRef.current.focus();
inputControlRef.current.blur();
onChangeCallback( dateTimeValue );
// Restore focus to the previously focused element
if (
previousFocusRef.current &&
previousFocusRef.current instanceof HTMLElement
) {
previousFocusRef.current.focus();
}
}
}, 0 );
},
[ onChangeCallback, value ]
);
const handleManualDateTimeChange = useCallback(
( newValue?: string ) => {
if ( newValue ) {
// Interpret the datetime-local value in WordPress timezone
const dateTime = getDate( newValue );
onChangeCallback( dateTime.toISOString() );
// Update calendar month to match
const parsedDate = parseDateTime( dateTime.toISOString() );
if ( parsedDate ) {
setCalendarMonth( parsedDate );
}
} else {
onChangeCallback( undefined );
}
},
[ onChangeCallback ]
);
const { format: fieldFormat } = field;
const weekStartsOn =
( fieldFormat as FormatDatetime ).weekStartsOn ??
getSettings().l10n.startOfWeek;
const {
timezone: { string: timezoneString },
} = getSettings();
let displayLabel = label;
if ( isValid?.required && ! markWhenOptional && ! hideLabelFromVision ) {
displayLabel = `${ label } (${ __( 'Required' ) })`;
} else if (
! isValid?.required &&
markWhenOptional &&
! hideLabelFromVision
) {
displayLabel = `${ label } (${ __( 'Optional' ) })`;
}
return (
{ /* Manual datetime input */ }
{ /* Calendar widget */ }
{ ! compact && (
) }
);
}
export default function DateTime< Item >( {
data,
field,
onChange,
hideLabelFromVision,
markWhenOptional,
operator,
validity,
config,
}: DataFormControlProps< Item > ) {
if ( operator === OPERATOR_IN_THE_PAST || operator === OPERATOR_OVER ) {
return (
);
}
return (
);
}