From e2440a3f3a6a607d6955dbf1d5f9b8efbb2186ee Mon Sep 17 00:00:00 2001 From: Adam Alston Date: Fri, 13 Feb 2026 18:28:25 -0500 Subject: [PATCH] fix(date-picker): avoid clearing epoch timestamp in range --- .../components/DatePicker/DatePicker-test.js | 37 ++++++++++++++++++- .../src/components/DatePicker/DatePicker.tsx | 26 +++++++++---- .../DatePicker/plugins/rangePlugin.ts | 5 ++- .../react/src/components/DatePicker/utils.ts | 9 +++++ 4 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 packages/react/src/components/DatePicker/utils.ts diff --git a/packages/react/src/components/DatePicker/DatePicker-test.js b/packages/react/src/components/DatePicker/DatePicker-test.js index 780d218896b6..87c9c958896f 100644 --- a/packages/react/src/components/DatePicker/DatePicker-test.js +++ b/packages/react/src/components/DatePicker/DatePicker-test.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import React, { useState } from 'react'; +import React, { createRef, useState } from 'react'; import DatePicker from './DatePicker'; import DatePickerInput from '../DatePickerInput'; import { render, screen, fireEvent } from '@testing-library/react'; @@ -1250,6 +1250,41 @@ describe('Range date picker', () => { expect(end.value).toBe('02/14/2025'); }); + it('should not clear an input when setDate receives timestamp 0', async () => { + const ref = createRef(); + + render( + + + + + ); + + const fp = ref.current.calendar; + const start = await screen.findByTestId('start-input-zero'); + const end = await screen.findByTestId('end-input-zero'); + const formattedZero = fp.formatDate(new Date(0), 'm/d/Y'); + const formattedOne = fp.formatDate(new Date(1), 'm/d/Y'); + + fp.setDate([0, 1], false, 'm/d/Y'); + expect(start.value).toBe(formattedZero); + expect(end.value).toBe(formattedOne); + + fp.setDate([1, 0], false, 'm/d/Y'); + expect(start.value).toBe(formattedOne); + expect(end.value).toBe(formattedZero); + }); + it('should not write both dates into the first input', async () => { const ref = React.createRef(); diff --git a/packages/react/src/components/DatePicker/DatePicker.tsx b/packages/react/src/components/DatePicker/DatePicker.tsx index 5d08c68c2132..dffe15b1b977 100644 --- a/packages/react/src/components/DatePicker/DatePicker.tsx +++ b/packages/react/src/components/DatePicker/DatePicker.tsx @@ -38,6 +38,7 @@ import { import type { Instance } from 'flatpickr/dist/types/instance'; import { datePartsOrder } from '@carbon/utilities'; import { SUPPORTED_LOCALES, type SupportedLocale } from './DatePickerLocales'; +import { isEmptyDateValue } from './utils'; // Weekdays shorthand for English locale // Ensure localization exists before trying to access it @@ -206,8 +207,10 @@ export type DatePickerTypes = 'simple' | 'single' | 'range'; export interface DatePickerProps { /** - * Flatpickr prop passthrough enables direct date input, and when set to false, - * we must clear dates manually by resetting the value prop to to a falsy value (such as `""`, `null`, or `undefined`) or an array of all falsy values, making it a controlled input. + * Flatpickr prop passthrough enables direct date input, and when set to + * false, we must clear dates manually by resetting the value prop to an empty + * value (such as `""`, `null`, or `undefined`) or an array of all empty + * values, making it a controlled input. */ allowInput?: boolean; @@ -912,10 +915,11 @@ const DatePicker = forwardRef((props, ref) => { useEffect(() => { // when value prop is manually reset, this clears the flatpickr calendar instance and text input // run if both: - // 1. value prop is set to a falsy value (`""`, `undefined`, `null`, etc) OR an array of all falsy values + // 1. value prop is set to an empty value (`""`, `undefined`, `null`, etc) OR an array of all empty values // 2. flatpickr instance contains values in its `selectedDates` property so it hasn't already been cleared if ( - (!value || (Array.isArray(value) && value.every((date) => !date))) && + (isEmptyDateValue(value) || + (Array.isArray(value) && value.every(isEmptyDateValue))) && calendarRef.current?.selectedDates.length ) { calendarRef.current?.clear(); @@ -938,7 +942,7 @@ const DatePicker = forwardRef((props, ref) => { value === '' || value === null || (Array.isArray(value) && - (value.length === 0 || value.every((element) => !element))) + (value.length === 0 || value.every(isEmptyDateValue))) ) { // only clear if there are selected dates to avoid unnecessary operations if (calendarRef.current.selectedDates.length > 0) { @@ -950,7 +954,11 @@ const DatePicker = forwardRef((props, ref) => { } updateClassNames(calendarRef.current, prefix); //for simple date picker w/o calendar; initial mount may not have value - } else if (!calendarRef.current && value) { + } else if ( + !calendarRef.current && + typeof value !== 'undefined' && + value !== null + ) { startInputField.current.value = value; } }, [value, prefix, startInputField]); @@ -992,8 +1000,10 @@ const DatePicker = forwardRef((props, ref) => { DatePicker.propTypes = { /** - * Flatpickr prop passthrough enables direct date input, and when set to false, - * we must clear dates manually by resetting the value prop to a falsy value (such as `""`, `null`, or `undefined`) or an array of all falsy values, making it a controlled input. + * Flatpickr prop passthrough enables direct date input, and when set to + * false, we must clear dates manually by resetting the value prop to an empty + * value (such as `""`, `null`, or `undefined`) or an array of all empty + * values, making it a controlled input. */ allowInput: PropTypes.bool, diff --git a/packages/react/src/components/DatePicker/plugins/rangePlugin.ts b/packages/react/src/components/DatePicker/plugins/rangePlugin.ts index ceb01b990fda..e10f905415a3 100644 --- a/packages/react/src/components/DatePicker/plugins/rangePlugin.ts +++ b/packages/react/src/components/DatePicker/plugins/rangePlugin.ts @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2019, 2025 + * Copyright IBM Corp. 2019, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -9,6 +9,7 @@ import baseRangePlugin, { type Config, } from 'flatpickr/dist/plugins/rangePlugin'; import { Instance } from 'flatpickr/dist/types/instance'; +import { isEmptyDateValue } from '../utils'; /** * @param config Plugin configuration. @@ -37,7 +38,7 @@ export const rangePlugin = (config: Config = {}) => { [inputFrom, inputToElement].forEach((input, i) => { if (input && input instanceof HTMLInputElement) { - input.value = !dates[i] + input.value = isEmptyDateValue(dates[i]) ? '' : formatDate(new Date(dates[i]), fp.config.dateFormat); } diff --git a/packages/react/src/components/DatePicker/utils.ts b/packages/react/src/components/DatePicker/utils.ts new file mode 100644 index 000000000000..e3c81f9fa126 --- /dev/null +++ b/packages/react/src/components/DatePicker/utils.ts @@ -0,0 +1,9 @@ +/** + * Copyright IBM Corp. 2026 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +export const isEmptyDateValue = (date: unknown) => + date === '' || date === null || typeof date === 'undefined';