From e93167f26e8781dbe63e8151d3d8e3e14d23168b Mon Sep 17 00:00:00 2001 From: Nikhil Tomar <63502271+2nikhiltom@users.noreply.github.com> Date: Tue, 6 Aug 2024 18:32:44 +0530 Subject: [PATCH] fix(16740): DatePicker calendar close issue on clickAway, DatePicker not running onChange events, DatePicker setting and clearing date in range issues (#17072) * fix: fixes DatePicker calendar close issue on clickAway * fix(DatePicker): fixes close calendar issue on click away * fix: covers empty value conditions * refactor: updates avt test as per 1.58 version * refactor: reverts changes made by renovate on avt test * refactor: removes callback and dependency from useeffect * refactor: callback is only added to onCalendarClose * refactor: deprecates the usage is setTimeout to use useEffect * refactor: revert storybook changes --- .../DatePicker/DatePicker-test.avt.e2e.js | 2 +- .../FluidDatePicker-test.avt.e2e.js | 2 +- .../src/components/DatePicker/DatePicker.tsx | 102 ++++++++++++++---- .../DatePicker/plugins/fixEventsPlugin.js | 16 +++ 4 files changed, 98 insertions(+), 24 deletions(-) diff --git a/e2e/components/DatePicker/DatePicker-test.avt.e2e.js b/e2e/components/DatePicker/DatePicker-test.avt.e2e.js index 94f9afde1c41..9c1e4f2f911b 100644 --- a/e2e/components/DatePicker/DatePicker-test.avt.e2e.js +++ b/e2e/components/DatePicker/DatePicker-test.avt.e2e.js @@ -125,7 +125,7 @@ test.describe('@avt DatePicker', () => { await page.keyboard.press('Enter'); await page.keyboard.press('Enter'); await expect( - page.locator('input#date-picker-input-id-finish') + page.locator('input#date-picker-input-id-start') ).toBeFocused(); await expect(page.locator('div.flatpickr-calendar')).not.toHaveClass( /open/ diff --git a/e2e/components/FluidDatePicker/FluidDatePicker-test.avt.e2e.js b/e2e/components/FluidDatePicker/FluidDatePicker-test.avt.e2e.js index 386c95ed1ad1..47a1bdc67042 100644 --- a/e2e/components/FluidDatePicker/FluidDatePicker-test.avt.e2e.js +++ b/e2e/components/FluidDatePicker/FluidDatePicker-test.avt.e2e.js @@ -98,7 +98,7 @@ test.describe('@avt FluidDatePicker', () => { await page.keyboard.press('ArrowDown'); await page.keyboard.press('Enter'); await expect( - page.locator('input#date-picker-input-id-finish') + page.locator('input#date-picker-input-id-start') ).toBeFocused(); await expect(page.locator('div.flatpickr-calendar')).not.toHaveClass( /open/ diff --git a/packages/react/src/components/DatePicker/DatePicker.tsx b/packages/react/src/components/DatePicker/DatePicker.tsx index a07ffe2d9f0a..e228653ac4c7 100644 --- a/packages/react/src/components/DatePicker/DatePicker.tsx +++ b/packages/react/src/components/DatePicker/DatePicker.tsx @@ -443,11 +443,17 @@ const DatePicker = React.forwardRef(function DatePicker( const lastStartValue = useRef(''); + interface CalendarCloseEvent { + selectedDates: Date[]; + dateStr: string; + instance: object; //This is `Intance` of flatpicker + } + const [calendarCloseEvent, setCalendarCloseEvent] = + useState(null); + // fix datepicker deleting the selectedDate when the calendar closes - const onCalendarClose = (selectedDates, dateStr) => { - endInputField?.current?.focus(); - calendarRef?.current?.calendarContainer?.classList.remove('open'); - setTimeout(() => { + const handleCalendarClose = useCallback( + (selectedDates, dateStr, instance) => { if ( lastStartValue.current && selectedDates[0] && @@ -461,21 +467,29 @@ const DatePicker = React.forwardRef(function DatePicker( ); } if (onClose) { - onClose( - calendarRef.current.selectedDates, - dateStr, - calendarRef.current - ); + onClose(selectedDates, dateStr, instance); } - }); + }, + [onClose] + ); + const onCalendarClose = (selectedDates, dateStr, instance, e) => { + if (e && e.type === 'clickOutside') { + return; + } + setCalendarCloseEvent({ selectedDates, dateStr, instance }); }; + useEffect(() => { + if (calendarCloseEvent) { + const { selectedDates, dateStr, instance } = calendarCloseEvent; + handleCalendarClose(selectedDates, dateStr, instance); + setCalendarCloseEvent(null); + } + }, [calendarCloseEvent, handleCalendarClose]); const endInputField = useRef(null); const calendarRef: any | undefined = useRef(null); const savedOnChange = useSavedCallback(onChange); - const savedOnClose = useSavedCallback( - datePickerType === 'range' ? onCalendarClose : onClose - ); + const savedOnOpen = useSavedCallback(onOpen); const datePickerClasses = cx(`${prefix}--date-picker`, { @@ -610,6 +624,7 @@ const DatePicker = React.forwardRef(function DatePicker( const { current: end } = endInputField; const flatpickerconfig: any = { inline: inline ?? false, + onClose: onCalendarClose, disableMobile: true, defaultDate: value, closeOnSelect: closeOnSelect, @@ -653,7 +668,7 @@ const DatePicker = React.forwardRef(function DatePicker( savedOnChange(...args); } }, - onClose: savedOnClose, + onReady: onHook, onMonthChange: onHook, onYearChange: onHook, @@ -772,14 +787,7 @@ const DatePicker = React.forwardRef(function DatePicker( } }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - savedOnChange, - savedOnClose, - savedOnOpen, - readOnly, - closeOnSelect, - hasInput, - ]); + }, [savedOnChange, savedOnOpen, readOnly, closeOnSelect, hasInput]); // this hook allows consumers to access the flatpickr calendar // instance for cases where functions like open() or close() @@ -831,6 +839,56 @@ const DatePicker = React.forwardRef(function DatePicker( calendarRef.current.set('inline', inline); } }, [inline]); + useEffect(() => { + //when value prop is set to empty, this clears the faltpicker's calendar instance and text input + if (value === '') { + calendarRef.current?.clear(); + if (startInputField.current) { + startInputField.current.value = ''; + } + + if (endInputField.current) { + endInputField.current.value = ''; + } + } + }, [value]); + + useEffect(() => { + let isMouseDown = false; + + const handleMouseDown = (event) => { + if ( + calendarRef.current && + calendarRef.current.isOpen && + !calendarRef.current.calendarContainer.contains(event.target) && + !startInputField.current.contains(event.target) && + !endInputField.current?.contains(event.target) + ) { + isMouseDown = true; + // Close the calendar immediately on mousedown + closeCalendar(event); + } + }; + + const closeCalendar = (event) => { + calendarRef.current.close(); + // Remove focus from endDate calendar input + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + onCalendarClose( + calendarRef.current.selectedDates, + '', + calendarRef.current, + { type: 'clickOutside' } + ); + }; + document.addEventListener('mousedown', handleMouseDown, true); + + return () => { + document.removeEventListener('mousedown', handleMouseDown, true); + }; + }, [calendarRef, startInputField, endInputField, onCalendarClose]); useEffect(() => { if (calendarRef?.current?.set) { diff --git a/packages/react/src/components/DatePicker/plugins/fixEventsPlugin.js b/packages/react/src/components/DatePicker/plugins/fixEventsPlugin.js index 8595c9cd6a77..e779049b72a5 100644 --- a/packages/react/src/components/DatePicker/plugins/fixEventsPlugin.js +++ b/packages/react/src/components/DatePicker/plugins/fixEventsPlugin.js @@ -13,6 +13,20 @@ import { match, keys } from '../../../internal/keyboard'; */ export default (config) => (fp) => { const { inputFrom, inputTo, lastStartValue } = config; + /** + * Handles `click` outside to close calendar + */ + const handleClickOutside = (event) => { + if ( + !fp.isOpen || + fp.calendarContainer.contains(event.target) || + event.target === inputFrom || + event.target === inputTo + ) { + return; + } + fp.close(); + }; /** * Handles `keydown` event. */ @@ -127,6 +141,7 @@ export default (config) => (fp) => { inputTo.removeEventListener('blur', handleBlur, true); } inputFrom.removeEventListener('keydown', handleKeydown, true); + document.removeEventListener('click', handleClickOutside, true); }; /** @@ -140,6 +155,7 @@ export default (config) => (fp) => { inputTo.addEventListener('keydown', handleKeydown, true); inputTo.addEventListener('blur', handleBlur, true); } + document.addEventListener('click', handleClickOutside, true); }; /**