Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
196 lines
7.8 KiB
Plaintext
196 lines
7.8 KiB
Plaintext
'use client';
|
|
|
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
import { TZDateMini as TZDate } from '@date-fns/tz/date/mini';
|
|
import { getTranslation } from '@payloadcms/translations';
|
|
import { transpose } from 'date-fns';
|
|
import { useCallback, useMemo } from 'react';
|
|
import { DatePickerField } from '../../elements/DatePicker/index.js';
|
|
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js';
|
|
import { TimezonePicker } from '../../elements/TimezonePicker/index.js';
|
|
import { FieldDescription } from '../../fields/FieldDescription/index.js';
|
|
import { FieldError } from '../../fields/FieldError/index.js';
|
|
import { FieldLabel } from '../../fields/FieldLabel/index.js';
|
|
import { useForm, useFormFields } from '../../forms/Form/context.js';
|
|
import { useField } from '../../forms/useField/index.js';
|
|
import './index.scss';
|
|
import { withCondition } from '../../forms/withCondition/index.js';
|
|
import { useConfig } from '../../providers/Config/index.js';
|
|
import { useTranslation } from '../../providers/Translation/index.js';
|
|
import { mergeFieldStyles } from '../mergeFieldStyles.js';
|
|
import { fieldBaseClass } from '../shared/index.js';
|
|
const baseClass = 'date-time-field';
|
|
const DateTimeFieldComponent = props => {
|
|
const {
|
|
field,
|
|
field: {
|
|
admin: {
|
|
className,
|
|
date: datePickerProps,
|
|
description,
|
|
placeholder
|
|
} = {},
|
|
label,
|
|
localized,
|
|
required,
|
|
timezone
|
|
},
|
|
path: pathFromProps,
|
|
readOnly,
|
|
validate
|
|
} = props;
|
|
const pickerAppearance = datePickerProps?.pickerAppearance || 'default';
|
|
// Get the user timezone so we can adjust the displayed value against it
|
|
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
const {
|
|
config
|
|
} = useConfig();
|
|
const {
|
|
i18n
|
|
} = useTranslation();
|
|
const {
|
|
dispatchFields,
|
|
setModified
|
|
} = useForm();
|
|
const memoizedValidate = useCallback((value, options) => {
|
|
if (typeof validate === 'function') {
|
|
return validate(value, {
|
|
...options,
|
|
required
|
|
});
|
|
}
|
|
}, [validate, required]);
|
|
const {
|
|
customComponents: {
|
|
AfterInput,
|
|
BeforeInput,
|
|
Description,
|
|
Error,
|
|
Label
|
|
} = {},
|
|
disabled,
|
|
path,
|
|
setValue,
|
|
showError,
|
|
value: value_0
|
|
} = useField({
|
|
potentiallyStalePath: pathFromProps,
|
|
validate: memoizedValidate
|
|
});
|
|
const timezonePath = path + '_tz';
|
|
const timezoneField = useFormFields(([fields, _]) => fields?.[timezonePath]);
|
|
const supportedTimezones = useMemo(() => {
|
|
if (timezone && typeof timezone === 'object' && timezone.supportedTimezones) {
|
|
return timezone.supportedTimezones;
|
|
}
|
|
return config.admin.timezones.supportedTimezones;
|
|
}, [config.admin.timezones.supportedTimezones, timezone]);
|
|
/**
|
|
* Date appearance doesn't include timestamps,
|
|
* which means we need to pin the time to always 12:00 for the selected date
|
|
*/
|
|
const isDateOnly = ['dayOnly', 'default', 'monthOnly'].includes(pickerAppearance);
|
|
const selectedTimezone = timezoneField?.value;
|
|
const timezoneRequired = required || timezone && typeof timezone === 'object' && timezone.required;
|
|
// The displayed value should be the original value, adjusted to the user's timezone
|
|
const displayedValue = useMemo(() => {
|
|
if (timezone && selectedTimezone && userTimezone && value_0) {
|
|
// Create TZDate instances for the selected timezone and the user's timezone
|
|
// These instances allow us to transpose the date between timezones while keeping the same time value
|
|
const DateWithOriginalTz = TZDate.tz(selectedTimezone);
|
|
const DateWithUserTz = TZDate.tz(userTimezone);
|
|
const modifiedDate = new TZDate(value_0).withTimeZone(selectedTimezone);
|
|
// Transpose the date to the selected timezone
|
|
const dateWithTimezone = transpose(modifiedDate, DateWithOriginalTz);
|
|
// Transpose the date to the user's timezone - this is necessary because the react-datepicker component insists on displaying the date in the user's timezone
|
|
const dateWithUserTimezone = transpose(dateWithTimezone, DateWithUserTz);
|
|
return dateWithUserTimezone.toISOString();
|
|
}
|
|
return value_0;
|
|
}, [timezone, selectedTimezone, value_0, userTimezone]);
|
|
const styles = useMemo(() => mergeFieldStyles(field), [field]);
|
|
const onChange = useCallback(incomingDate => {
|
|
if (!(readOnly || disabled)) {
|
|
if (timezone && selectedTimezone && incomingDate) {
|
|
// Create TZDate instances for the selected timezone
|
|
const TZDateWithSelectedTz = TZDate.tz(selectedTimezone);
|
|
if (isDateOnly) {
|
|
// We need to offset this hardcoded hour offset from the DatePicker elemenent
|
|
// this can be removed in 4.0 when we remove the hardcoded offset as it is a breaking change
|
|
// const tzOffset = incomingDate.getTimezoneOffset() / 60
|
|
const incomingOffset = incomingDate.getTimezoneOffset() / 60;
|
|
const originalHour = incomingDate.getHours() + incomingOffset;
|
|
incomingDate.setHours(originalHour);
|
|
// Convert the original date as picked into the desired timezone.
|
|
const dateToSelectedTz = transpose(incomingDate, TZDateWithSelectedTz);
|
|
setValue(dateToSelectedTz.toISOString() || null);
|
|
} else {
|
|
// Creates a TZDate instance for the user's timezone — this is default behaviour of TZDate as it wraps the Date constructor
|
|
const dateToUserTz = new TZDate(incomingDate);
|
|
// Transpose the date to the selected timezone
|
|
const dateWithTimezone_0 = transpose(dateToUserTz, TZDateWithSelectedTz);
|
|
setValue(dateWithTimezone_0.toISOString() || null);
|
|
}
|
|
} else {
|
|
setValue(incomingDate?.toISOString() || null);
|
|
}
|
|
}
|
|
}, [readOnly, disabled, timezone, selectedTimezone, isDateOnly, setValue]);
|
|
const onChangeTimezone = useCallback(timezone_0 => {
|
|
if (timezonePath) {
|
|
dispatchFields({
|
|
type: 'UPDATE',
|
|
path: timezonePath,
|
|
value: timezone_0
|
|
});
|
|
setModified(true);
|
|
}
|
|
}, [dispatchFields, setModified, timezonePath]);
|
|
return /*#__PURE__*/_jsxs("div", {
|
|
className: [fieldBaseClass, baseClass, className, showError && `${baseClass}--has-error`, (readOnly || disabled) && 'read-only'].filter(Boolean).join(' '),
|
|
style: styles,
|
|
children: [/*#__PURE__*/_jsx(RenderCustomComponent, {
|
|
CustomComponent: Label,
|
|
Fallback: /*#__PURE__*/_jsx(FieldLabel, {
|
|
label: label,
|
|
localized: localized,
|
|
path: path,
|
|
required: required
|
|
})
|
|
}), /*#__PURE__*/_jsxs("div", {
|
|
className: `${fieldBaseClass}__wrap`,
|
|
id: `field-${path.replace(/\./g, '__')}`,
|
|
children: [/*#__PURE__*/_jsx(RenderCustomComponent, {
|
|
CustomComponent: Error,
|
|
Fallback: /*#__PURE__*/_jsx(FieldError, {
|
|
path: path,
|
|
showError: showError
|
|
})
|
|
}), BeforeInput, /*#__PURE__*/_jsx(DatePickerField, {
|
|
...datePickerProps,
|
|
onChange: onChange,
|
|
overrides: {
|
|
...datePickerProps?.overrides
|
|
},
|
|
placeholder: getTranslation(placeholder, i18n),
|
|
readOnly: readOnly || disabled,
|
|
value: displayedValue
|
|
}), timezone && supportedTimezones.length > 0 && /*#__PURE__*/_jsx(TimezonePicker, {
|
|
id: `${path}-timezone-picker`,
|
|
onChange: onChangeTimezone,
|
|
options: supportedTimezones,
|
|
readOnly: readOnly || disabled,
|
|
required: timezoneRequired,
|
|
selectedTimezone: selectedTimezone
|
|
}), AfterInput]
|
|
}), /*#__PURE__*/_jsx(RenderCustomComponent, {
|
|
CustomComponent: Description,
|
|
Fallback: /*#__PURE__*/_jsx(FieldDescription, {
|
|
description: description,
|
|
path: path
|
|
})
|
|
})]
|
|
});
|
|
};
|
|
export const DateTimeField = withCondition(DateTimeFieldComponent);
|
|
//# sourceMappingURL=index.js.map |