From 03cb1bbde97772f283382dac5de54c6321bf7fab Mon Sep 17 00:00:00 2001 From: tewarig Date: Wed, 6 Nov 2024 23:21:32 +0530 Subject: [PATCH 01/27] chore: trigger native events --- .../components/DatePicker/DatePicker.web.tsx | 22 ++++++++++++-- .../src/components/Dropdown/useDropdown.ts | 6 ++++ .../components/FileUpload/FileUpload.web.tsx | 3 ++ .../fireNativeEvent/fireNativeEvent.web.ts | 29 +++++++++++++++++++ .../blade/src/utils/fireNativeEvent/index.ts | 1 + 5 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts create mode 100644 packages/blade/src/utils/fireNativeEvent/index.ts diff --git a/packages/blade/src/components/DatePicker/DatePicker.web.tsx b/packages/blade/src/components/DatePicker/DatePicker.web.tsx index 881bd8119f6..64a3202207d 100644 --- a/packages/blade/src/components/DatePicker/DatePicker.web.tsx +++ b/packages/blade/src/components/DatePicker/DatePicker.web.tsx @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { DatesProvider } from '@mantine/dates'; -import React from 'react'; +import React, { useRef, useEffect } from 'react'; import { FloatingFocusManager, FloatingPortal } from '@floating-ui/react'; import { useI18nContext } from '@razorpay/i18nify-react'; import { MantineProvider } from '@mantine/core'; @@ -34,6 +34,7 @@ import type { StyledPropsBlade } from '~components/Box/styledProps'; import { getStyledProps } from '~components/Box/styledProps'; import { metaAttribute, MetaConstants } from '~utils/metaAttribute'; import { componentZIndices } from '~utils/componentZIndices'; +import { fireNativeEvent } from '~utils/fireNativeEvent'; const DatePicker = ({ selectionType, @@ -71,6 +72,8 @@ const DatePicker = ({ const isSingle = _selectionType === 'single'; const [_, forceRerender] = React.useReducer((x: number) => x + 1, 0); const [selectedPreset, setSelectedPreset] = React.useState(null); + const referenceRef = React.useRef(null); + const parentRef = useRef(null); const [_picker, setPicker] = useControllableState({ defaultValue: defaultPicker, @@ -97,6 +100,7 @@ const DatePicker = ({ defaultValue, onChange: (date) => { onChange?.(date as never); + fireNativeEvent(referenceRef, date as never, ['input']); if (isSingle) return; // sync selected preset with value setSelectedPreset(date as DatesRangeValue); @@ -124,6 +128,7 @@ const DatePicker = ({ const handleApply = (): void => { if (isSingle) { onChange?.(controlledValue); + fireNativeEvent(referenceRef, controlledValue, ['change']); setOldValue(controlledValue); onApply?.(controlledValue); close(); @@ -132,6 +137,7 @@ const DatePicker = ({ // only apply if both dates are selected if (hasBothDatesSelected) { onChange?.(controlledValue); + fireNativeEvent(referenceRef, controlledValue, ['change']); setOldValue(controlledValue); onApply?.(controlledValue); close(); @@ -140,6 +146,7 @@ const DatePicker = ({ const handleCancel = (): void => { setControlledValue(oldValue); + fireNativeEvent(referenceRef, oldValue, ['change']); setPickedDate(null); close(); }; @@ -147,7 +154,6 @@ const DatePicker = ({ const isMobile = useIsMobile(); const defaultInitialFocusRef = React.useRef(null); const titleId = useId('datepicker-title'); - const referenceRef = React.useRef(null); const { context, refs, @@ -268,6 +274,17 @@ const DatePicker = ({ logger({ type: 'warn', message: 'Failed to load dayjs locale' }); } }, [i18nState?.locale]); + // TODO: remove this useEffect + useEffect(() => { + if (parentRef.current) { + parentRef.current.addEventListener('change', (e: any) => { + console.log('Date changed -> change event', e); + }); + parentRef.current.addEventListener('input', (e: any) => { + console.log('Date changed -> input', e); + }); + } + }, [parentRef]); return ( @@ -276,6 +293,7 @@ const DatePicker = ({ width="100%" {...getStyledProps(props)} {...metaAttribute({ name: MetaConstants.DatePicker })} + ref={parentRef} > {}; @@ -355,6 +356,11 @@ const useDropdown = (): UseDropdownReturnValue => { const optionValues = options.map((option) => option.value); ensureScrollVisiblity(updatedIndex, rest.actionListItemRef.current, optionValues); + fireNativeEvent( + rest.actionListItemRef as React.RefObject, + options[updatedIndex].value, + ['change', 'input'], + ); }; /** diff --git a/packages/blade/src/components/FileUpload/FileUpload.web.tsx b/packages/blade/src/components/FileUpload/FileUpload.web.tsx index 64fc3fb1fcd..a2145f6b79a 100644 --- a/packages/blade/src/components/FileUpload/FileUpload.web.tsx +++ b/packages/blade/src/components/FileUpload/FileUpload.web.tsx @@ -26,6 +26,7 @@ import { makeAccessible } from '~utils/makeAccessible'; import { formHintLeftLabelMarginLeft } from '~components/Input/BaseInput/baseInputTokens'; import { useMergeRefs } from '~utils/useMergeRefs'; import { useControllableState } from '~utils/useControllable'; +import { fireNativeEvent } from '~utils/fireNativeEvent'; const _FileUpload: React.ForwardRefRenderFunction = ( { @@ -158,6 +159,7 @@ const _FileUpload: React.ForwardRefRenderFunction | null, + value: DatesRangeValue | File[] | string | null, + eventTypes: Array<'change' | 'input'>, +): void => { + if (ref) { + eventTypes.forEach((eventType) => { + const event = new Event(eventType, { bubbles: true }); + Object.defineProperty(event, 'target', { value: { value }, writable: false }); + ref.current?.dispatchEvent(event); + }); + } else { + console.log('ref is not defined'); + } +}; diff --git a/packages/blade/src/utils/fireNativeEvent/index.ts b/packages/blade/src/utils/fireNativeEvent/index.ts new file mode 100644 index 00000000000..13d751eea57 --- /dev/null +++ b/packages/blade/src/utils/fireNativeEvent/index.ts @@ -0,0 +1 @@ +export * from './fireNativeEvent.web'; From 48182f72138db0508ff90365d4fc74a72964ca5b Mon Sep 17 00:00:00 2001 From: tewarig Date: Wed, 6 Nov 2024 23:21:37 +0530 Subject: [PATCH 02/27] chore: trigger native events --- .../src/utils/fireNativeEvent/fireNativeEvent.web.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts index fd8cdd0562b..a5ed399a0d1 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts @@ -1,17 +1,5 @@ import type { DatesRangeValue } from '@mantine/dates'; -// interface valueType { -// value: DatesRangeValue | null; -// } - -// interface eventType { -// eventType: 'change' | 'input' as 'string'; -// } - -// interface optionsType { -// bubbles?: boolean; -// writable?: boolean; -// } export const fireNativeEvent = ( ref: React.RefObject | null, value: DatesRangeValue | File[] | string | null, From 0c355c1b14c78608e7b4f1395b651c299dacc34a Mon Sep 17 00:00:00 2001 From: tewarig Date: Thu, 7 Nov 2024 09:12:30 +0530 Subject: [PATCH 03/27] fix: fireNativeEvents in case of dropdown --- .../components/FileUpload/FileUpload.web.tsx | 2 +- .../DropdownInputTriggers/AutoComplete.tsx | 4 +++- .../BaseDropdownInputTrigger.tsx | 22 ++++++++++++------- .../fireNativeEvent/fireNativeEvent.web.ts | 4 ++-- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/blade/src/components/FileUpload/FileUpload.web.tsx b/packages/blade/src/components/FileUpload/FileUpload.web.tsx index a2145f6b79a..a412f7b1fd6 100644 --- a/packages/blade/src/components/FileUpload/FileUpload.web.tsx +++ b/packages/blade/src/components/FileUpload/FileUpload.web.tsx @@ -171,8 +171,8 @@ const _FileUpload: React.ForwardRefRenderFunction option.value === values[0])?.title; props.onInputValueChange?.({ @@ -131,10 +133,10 @@ const useAutoComplete = ({ if (typeof props.value === 'undefined') { setInputValue(displayText ?? ''); } + fireNativeEvent(triggererRef, displayText ?? '', ['input', 'change']); } props.onChange?.({ name: props.name, values }); }; - return { onSelectionChange, onTriggerKeydown, diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx index 649566c48f7..a02826696fd 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx +++ b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx @@ -18,6 +18,7 @@ import { validationStateToInputTrailingIconMap, } from '~components/Table/tokens'; import { useTableEditableCell } from '~components/Table/TableEditableCellContext'; +import { fireNativeEvent } from '~utils/fireNativeEvent'; const useControlledDropdownInput = ( props: Pick< @@ -30,6 +31,7 @@ const useControlledDropdownInput = ( | 'syncInputValueWithSelection' | 'isSelectInput' >, + triggererRef: React.RefObject, ): void => { const isFirstRender = useFirstRender(); const { @@ -116,6 +118,7 @@ const useControlledDropdownInput = ( name: props.name, values: getValuesArrayFromIndices(), }); + fireNativeEvent(triggererRef, getValuesArrayFromIndices(), ['change', 'input']); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [changeCallbackTriggerer]); @@ -169,14 +172,17 @@ const _BaseDropdownInputTrigger = ( return isOpen; }, [hasAutoCompleteInBottomSheetHeader, props.isSelectInput, isOpen]); - useControlledDropdownInput({ - onChange: props.onChange, - name: props.name, - value: props.value, - defaultValue: props.defaultValue, - syncInputValueWithSelection: props.syncInputValueWithSelection, - isSelectInput: props.isSelectInput, - }); + useControlledDropdownInput( + { + onChange: props.onChange, + name: props.name, + value: props.value, + defaultValue: props.defaultValue, + syncInputValueWithSelection: props.syncInputValueWithSelection, + isSelectInput: props.isSelectInput, + }, + triggererRef, + ); const getValue = (): string | undefined => { let prefix = ''; diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts index a5ed399a0d1..c3e409077e7 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts @@ -2,7 +2,7 @@ import type { DatesRangeValue } from '@mantine/dates'; export const fireNativeEvent = ( ref: React.RefObject | null, - value: DatesRangeValue | File[] | string | null, + value: DatesRangeValue | File[] | string | null | number[] | string[] | number, eventTypes: Array<'change' | 'input'>, ): void => { if (ref) { @@ -12,6 +12,6 @@ export const fireNativeEvent = ( ref.current?.dispatchEvent(event); }); } else { - console.log('ref is not defined'); + console.warn('ref is not defined'); } }; From 90d7063bdbcddedfa447e94ccb298c3cfa0803b7 Mon Sep 17 00:00:00 2001 From: tewarig Date: Thu, 7 Nov 2024 09:19:38 +0530 Subject: [PATCH 04/27] chore: remove extra code --- .../src/components/DatePicker/DatePicker.web.tsx | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/blade/src/components/DatePicker/DatePicker.web.tsx b/packages/blade/src/components/DatePicker/DatePicker.web.tsx index 64a3202207d..d8a8882b3f3 100644 --- a/packages/blade/src/components/DatePicker/DatePicker.web.tsx +++ b/packages/blade/src/components/DatePicker/DatePicker.web.tsx @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { DatesProvider } from '@mantine/dates'; -import React, { useRef, useEffect } from 'react'; +import React from 'react'; import { FloatingFocusManager, FloatingPortal } from '@floating-ui/react'; import { useI18nContext } from '@razorpay/i18nify-react'; import { MantineProvider } from '@mantine/core'; @@ -73,7 +73,6 @@ const DatePicker = ({ const [_, forceRerender] = React.useReducer((x: number) => x + 1, 0); const [selectedPreset, setSelectedPreset] = React.useState(null); const referenceRef = React.useRef(null); - const parentRef = useRef(null); const [_picker, setPicker] = useControllableState({ defaultValue: defaultPicker, @@ -274,17 +273,6 @@ const DatePicker = ({ logger({ type: 'warn', message: 'Failed to load dayjs locale' }); } }, [i18nState?.locale]); - // TODO: remove this useEffect - useEffect(() => { - if (parentRef.current) { - parentRef.current.addEventListener('change', (e: any) => { - console.log('Date changed -> change event', e); - }); - parentRef.current.addEventListener('input', (e: any) => { - console.log('Date changed -> input', e); - }); - } - }, [parentRef]); return ( @@ -293,7 +281,6 @@ const DatePicker = ({ width="100%" {...getStyledProps(props)} {...metaAttribute({ name: MetaConstants.DatePicker })} - ref={parentRef} > Date: Thu, 7 Nov 2024 09:50:49 +0530 Subject: [PATCH 05/27] chore: change value of fireNative events to unknown --- .../blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts index c3e409077e7..8da522c1d03 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts @@ -1,8 +1,6 @@ -import type { DatesRangeValue } from '@mantine/dates'; - export const fireNativeEvent = ( ref: React.RefObject | null, - value: DatesRangeValue | File[] | string | null | number[] | string[] | number, + value: unknown, eventTypes: Array<'change' | 'input'>, ): void => { if (ref) { From d75fa174db146b6daeec2b6c3c9ef7bd88cfd0a2 Mon Sep 17 00:00:00 2001 From: tewarig Date: Thu, 7 Nov 2024 23:30:49 +0530 Subject: [PATCH 06/27] chore: remove fireNativeEvent from autocompelte --- .../Input/DropdownInputTriggers/AutoComplete.tsx | 3 --- .../DropdownInputTriggers/BaseDropdownInputTrigger.tsx | 7 ++++++- .../blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/AutoComplete.tsx b/packages/blade/src/components/Input/DropdownInputTriggers/AutoComplete.tsx index 35737dd3c00..635b8173caa 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/AutoComplete.tsx +++ b/packages/blade/src/components/Input/DropdownInputTriggers/AutoComplete.tsx @@ -7,7 +7,6 @@ import BaseBox from '~components/Box/BaseBox'; import type { BladeElementRef } from '~utils/types'; import { dropdownComponentIds } from '~components/Dropdown/dropdownComponentIds'; import { isReactNative } from '~utils'; -import { fireNativeEvent } from '~utils/fireNativeEvent'; const useAutoComplete = ({ props, @@ -122,7 +121,6 @@ const useAutoComplete = ({ props.onInputValueChange?.({ name: props.name, value: '' }); setActiveTagIndex(-1); resetFilters(); - fireNativeEvent(triggererRef, values, ['input', 'change']); } else { const displayText = options.find((option) => option.value === values[0])?.title; props.onInputValueChange?.({ @@ -133,7 +131,6 @@ const useAutoComplete = ({ if (typeof props.value === 'undefined') { setInputValue(displayText ?? ''); } - fireNativeEvent(triggererRef, displayText ?? '', ['input', 'change']); } props.onChange?.({ name: props.name, values }); }; diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx index a02826696fd..18b97f6a802 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx +++ b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx @@ -118,7 +118,12 @@ const useControlledDropdownInput = ( name: props.name, values: getValuesArrayFromIndices(), }); - fireNativeEvent(triggererRef, getValuesArrayFromIndices(), ['change', 'input']); + fireNativeEvent( + triggererRef, + getValuesArrayFromIndices(), + ['change', 'input'], + 'BaseDropdownInputTrigger-useControlledDropdownInput', + ); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [changeCallbackTriggerer]); diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts index 8da522c1d03..e90dacd14c5 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts @@ -2,7 +2,9 @@ export const fireNativeEvent = ( ref: React.RefObject | null, value: unknown, eventTypes: Array<'change' | 'input'>, + calledFrom?: string, ): void => { + console.log('fireNativeEvent called from:', calledFrom); if (ref) { eventTypes.forEach((eventType) => { const event = new Event(eventType, { bubbles: true }); From 5d167443e7ba6b70603e3c11c769eed786174f35 Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 8 Nov 2024 00:24:45 +0530 Subject: [PATCH 07/27] chore: remove add fireNative event on remove , dissmiss --- packages/blade/src/components/FileUpload/FileUpload.web.tsx | 6 +++++- .../blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/blade/src/components/FileUpload/FileUpload.web.tsx b/packages/blade/src/components/FileUpload/FileUpload.web.tsx index a412f7b1fd6..b51548a4ef3 100644 --- a/packages/blade/src/components/FileUpload/FileUpload.web.tsx +++ b/packages/blade/src/components/FileUpload/FileUpload.web.tsx @@ -172,7 +172,6 @@ const _FileUpload: React.ForwardRefRenderFunction id !== selectedFiles[0].id); setSelectedFiles(() => newFiles); onRemove?.({ file: selectedFiles[0] }); + fireNativeEvent(inputRef, [], ['change', 'input']); }} onReupload={() => { const newFiles = selectedFiles.filter(({ id }) => id !== selectedFiles[0].id); @@ -371,6 +371,8 @@ const _FileUpload: React.ForwardRefRenderFunction id !== file.id); setSelectedFiles(() => newFiles); onRemove?.({ file }); + fireNativeEvent(inputRef, [], ['change', 'input']); + console.log('onRemove'); }} onReupload={() => { const newFiles = selectedFiles.filter(({ id }) => id !== file.id); @@ -389,6 +391,8 @@ const _FileUpload: React.ForwardRefRenderFunction id !== file.id); setSelectedFiles(() => newFiles); onDismiss?.({ file }); + fireNativeEvent(inputRef, [], ['change', 'input']); + console.log('onDismiss'); }} onPreview={onPreview} /> diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts index e90dacd14c5..881a990981b 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts @@ -8,7 +8,6 @@ export const fireNativeEvent = ( if (ref) { eventTypes.forEach((eventType) => { const event = new Event(eventType, { bubbles: true }); - Object.defineProperty(event, 'target', { value: { value }, writable: false }); ref.current?.dispatchEvent(event); }); } else { From 45b43f9bca84da8b630a9365085136ae5d20f7ee Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 8 Nov 2024 00:30:37 +0530 Subject: [PATCH 08/27] chore: remove logs --- packages/blade/src/components/FileUpload/FileUpload.web.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/blade/src/components/FileUpload/FileUpload.web.tsx b/packages/blade/src/components/FileUpload/FileUpload.web.tsx index b51548a4ef3..03d7825b1e1 100644 --- a/packages/blade/src/components/FileUpload/FileUpload.web.tsx +++ b/packages/blade/src/components/FileUpload/FileUpload.web.tsx @@ -372,7 +372,6 @@ const _FileUpload: React.ForwardRefRenderFunction newFiles); onRemove?.({ file }); fireNativeEvent(inputRef, [], ['change', 'input']); - console.log('onRemove'); }} onReupload={() => { const newFiles = selectedFiles.filter(({ id }) => id !== file.id); @@ -392,7 +391,6 @@ const _FileUpload: React.ForwardRefRenderFunction newFiles); onDismiss?.({ file }); fireNativeEvent(inputRef, [], ['change', 'input']); - console.log('onDismiss'); }} onPreview={onPreview} /> From b68344040f22d7bc0e27cbb3175c3798226a1334 Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 8 Nov 2024 00:55:42 +0530 Subject: [PATCH 09/27] chore: code clean up --- .../blade/src/components/DatePicker/DatePicker.web.tsx | 8 ++++---- packages/blade/src/components/Dropdown/useDropdown.ts | 6 +----- .../blade/src/components/FileUpload/FileUpload.web.tsx | 8 ++++---- .../DropdownInputTriggers/BaseDropdownInputTrigger.tsx | 7 +------ .../src/utils/fireNativeEvent/fireNativeEvent.web.ts | 3 --- 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/blade/src/components/DatePicker/DatePicker.web.tsx b/packages/blade/src/components/DatePicker/DatePicker.web.tsx index d8a8882b3f3..d677df711f8 100644 --- a/packages/blade/src/components/DatePicker/DatePicker.web.tsx +++ b/packages/blade/src/components/DatePicker/DatePicker.web.tsx @@ -99,7 +99,7 @@ const DatePicker = ({ defaultValue, onChange: (date) => { onChange?.(date as never); - fireNativeEvent(referenceRef, date as never, ['input']); + fireNativeEvent(referenceRef, ['input']); if (isSingle) return; // sync selected preset with value setSelectedPreset(date as DatesRangeValue); @@ -127,7 +127,7 @@ const DatePicker = ({ const handleApply = (): void => { if (isSingle) { onChange?.(controlledValue); - fireNativeEvent(referenceRef, controlledValue, ['change']); + fireNativeEvent(referenceRef, ['change']); setOldValue(controlledValue); onApply?.(controlledValue); close(); @@ -136,7 +136,7 @@ const DatePicker = ({ // only apply if both dates are selected if (hasBothDatesSelected) { onChange?.(controlledValue); - fireNativeEvent(referenceRef, controlledValue, ['change']); + fireNativeEvent(referenceRef, ['change']); setOldValue(controlledValue); onApply?.(controlledValue); close(); @@ -145,7 +145,7 @@ const DatePicker = ({ const handleCancel = (): void => { setControlledValue(oldValue); - fireNativeEvent(referenceRef, oldValue, ['change']); + fireNativeEvent(referenceRef, ['change']); setPickedDate(null); close(); }; diff --git a/packages/blade/src/components/Dropdown/useDropdown.ts b/packages/blade/src/components/Dropdown/useDropdown.ts index 7cb46d54a47..806db93dac3 100644 --- a/packages/blade/src/components/Dropdown/useDropdown.ts +++ b/packages/blade/src/components/Dropdown/useDropdown.ts @@ -356,11 +356,7 @@ const useDropdown = (): UseDropdownReturnValue => { const optionValues = options.map((option) => option.value); ensureScrollVisiblity(updatedIndex, rest.actionListItemRef.current, optionValues); - fireNativeEvent( - rest.actionListItemRef as React.RefObject, - options[updatedIndex].value, - ['change', 'input'], - ); + fireNativeEvent(rest.actionListItemRef as React.RefObject, ['change', 'input']); }; /** diff --git a/packages/blade/src/components/FileUpload/FileUpload.web.tsx b/packages/blade/src/components/FileUpload/FileUpload.web.tsx index 03d7825b1e1..687e24516bb 100644 --- a/packages/blade/src/components/FileUpload/FileUpload.web.tsx +++ b/packages/blade/src/components/FileUpload/FileUpload.web.tsx @@ -159,7 +159,7 @@ const _FileUpload: React.ForwardRefRenderFunction id !== selectedFiles[0].id); setSelectedFiles(() => newFiles); onRemove?.({ file: selectedFiles[0] }); - fireNativeEvent(inputRef, [], ['change', 'input']); + fireNativeEvent(inputRef, ['change', 'input']); }} onReupload={() => { const newFiles = selectedFiles.filter(({ id }) => id !== selectedFiles[0].id); @@ -371,7 +371,7 @@ const _FileUpload: React.ForwardRefRenderFunction id !== file.id); setSelectedFiles(() => newFiles); onRemove?.({ file }); - fireNativeEvent(inputRef, [], ['change', 'input']); + fireNativeEvent(inputRef, ['change', 'input']); }} onReupload={() => { const newFiles = selectedFiles.filter(({ id }) => id !== file.id); @@ -390,7 +390,7 @@ const _FileUpload: React.ForwardRefRenderFunction id !== file.id); setSelectedFiles(() => newFiles); onDismiss?.({ file }); - fireNativeEvent(inputRef, [], ['change', 'input']); + fireNativeEvent(inputRef, ['change', 'input']); }} onPreview={onPreview} /> diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx index 18b97f6a802..b29572e9b45 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx +++ b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx @@ -118,12 +118,7 @@ const useControlledDropdownInput = ( name: props.name, values: getValuesArrayFromIndices(), }); - fireNativeEvent( - triggererRef, - getValuesArrayFromIndices(), - ['change', 'input'], - 'BaseDropdownInputTrigger-useControlledDropdownInput', - ); + fireNativeEvent(triggererRef, ['change', 'input']); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [changeCallbackTriggerer]); diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts index 881a990981b..860acbbd008 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts @@ -1,10 +1,7 @@ export const fireNativeEvent = ( ref: React.RefObject | null, - value: unknown, eventTypes: Array<'change' | 'input'>, - calledFrom?: string, ): void => { - console.log('fireNativeEvent called from:', calledFrom); if (ref) { eventTypes.forEach((eventType) => { const event = new Event(eventType, { bubbles: true }); From f66ef5fa15cd1db5a01f4e4537e4c1f7df2513f4 Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 8 Nov 2024 01:10:57 +0530 Subject: [PATCH 10/27] chore: minor change --- .../BaseDropdownInputTrigger.tsx | 20 +++++++++---------- .../Input/DropdownInputTriggers/types.ts | 4 ++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx index b29572e9b45..96e97f67421 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx +++ b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx @@ -30,8 +30,8 @@ const useControlledDropdownInput = ( | 'onInputValueChange' | 'syncInputValueWithSelection' | 'isSelectInput' + | 'triggererRef' >, - triggererRef: React.RefObject, ): void => { const isFirstRender = useFirstRender(); const { @@ -172,17 +172,15 @@ const _BaseDropdownInputTrigger = ( return isOpen; }, [hasAutoCompleteInBottomSheetHeader, props.isSelectInput, isOpen]); - useControlledDropdownInput( - { - onChange: props.onChange, - name: props.name, - value: props.value, - defaultValue: props.defaultValue, - syncInputValueWithSelection: props.syncInputValueWithSelection, - isSelectInput: props.isSelectInput, - }, + useControlledDropdownInput({ + onChange: props.onChange, + name: props.name, + value: props.value, + defaultValue: props.defaultValue, + syncInputValueWithSelection: props.syncInputValueWithSelection, + isSelectInput: props.isSelectInput, triggererRef, - ); + }); const getValue = (): string | undefined => { let prefix = ''; diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/types.ts b/packages/blade/src/components/Input/DropdownInputTriggers/types.ts index 9ae131d9f24..7af578663a9 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/types.ts +++ b/packages/blade/src/components/Input/DropdownInputTriggers/types.ts @@ -125,6 +125,10 @@ export type BaseDropdownInputTriggerProps = Omit< * Internal prop to handle click on input trigger */ onTriggerClick: BaseInputProps['onClick']; + /** + * Internal prop to get ref of trigger element + */ + triggererRef: React.RefObject; }; export type SelectInputProps = DropdownInputTriggersProps; From 6440afd39ee8fd8971756e19d6f098155051ba99 Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 8 Nov 2024 01:22:15 +0530 Subject: [PATCH 11/27] chore: minor changes --- .../BaseDropdownInputTrigger.tsx | 2 +- .../fireNativeEvent/fireNativeEvent.web.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx index 96e97f67421..4b8de317e46 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx +++ b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx @@ -118,7 +118,7 @@ const useControlledDropdownInput = ( name: props.name, values: getValuesArrayFromIndices(), }); - fireNativeEvent(triggererRef, ['change', 'input']); + fireNativeEvent(props.triggererRef, ['change', 'input']); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [changeCallbackTriggerer]); diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts index 860acbbd008..d67eaa7ef44 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts @@ -1,3 +1,20 @@ +/** + * Fires native events on a given HTML element reference. + * + * @param ref - A React ref object pointing to an HTML element or null. + * @param eventTypes - An array of event types to be dispatched. Supported event types are 'change' and 'input'. + * + * @remarks + * This function creates and dispatches native events of the specified types on the element referenced by `ref`. + * If `ref` is null, a warning is logged to the console. + * + * @example + * ```typescript + * const inputRef = React.createRef(); + * fireNativeEvent(inputRef, ['change', 'input']); + * ``` + */ + export const fireNativeEvent = ( ref: React.RefObject | null, eventTypes: Array<'change' | 'input'>, From 497aff1738cce15333f529b0fa25e9e14a48f6c3 Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 8 Nov 2024 01:42:48 +0530 Subject: [PATCH 12/27] chore: fix type --- .../BaseDropdownInputTrigger.tsx | 16 ++-------------- .../Input/DropdownInputTriggers/types.ts | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx index 4b8de317e46..4344441698b 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx +++ b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { BaseInput } from '../BaseInput'; import type { BaseInputProps } from '../BaseInput'; import { InputChevronIcon } from './InputChevronIcon'; -import type { BaseDropdownInputTriggerProps } from './types'; +import type { BaseDropdownInputTriggerProps, useControlledDropdownInputProps } from './types'; import isEmpty from '~utils/lodashButBetter/isEmpty'; import { useDropdown } from '~components/Dropdown/useDropdown'; import { isReactNative } from '~utils'; @@ -20,19 +20,7 @@ import { import { useTableEditableCell } from '~components/Table/TableEditableCellContext'; import { fireNativeEvent } from '~utils/fireNativeEvent'; -const useControlledDropdownInput = ( - props: Pick< - BaseDropdownInputTriggerProps, - | 'onChange' - | 'name' - | 'value' - | 'defaultValue' - | 'onInputValueChange' - | 'syncInputValueWithSelection' - | 'isSelectInput' - | 'triggererRef' - >, -): void => { +const useControlledDropdownInput = (props: useControlledDropdownInputProps): void => { const isFirstRender = useFirstRender(); const { changeCallbackTriggerer, diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/types.ts b/packages/blade/src/components/Input/DropdownInputTriggers/types.ts index 7af578663a9..90490ec49a7 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/types.ts +++ b/packages/blade/src/components/Input/DropdownInputTriggers/types.ts @@ -125,9 +125,18 @@ export type BaseDropdownInputTriggerProps = Omit< * Internal prop to handle click on input trigger */ onTriggerClick: BaseInputProps['onClick']; - /** - * Internal prop to get ref of trigger element - */ +}; + +export type useControlledDropdownInputProps = Pick< + BaseDropdownInputTriggerProps, + | 'onChange' + | 'name' + | 'value' + | 'defaultValue' + | 'onInputValueChange' + | 'syncInputValueWithSelection' + | 'isSelectInput' +> & { triggererRef: React.RefObject; }; From bf6083f6753d2efbaa6893a38ebeb57175c8a35c Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 8 Nov 2024 15:45:30 +0530 Subject: [PATCH 13/27] chore: fire native event is not supported on react-native --- .../src/utils/fireNativeEvent/fireNativeEvent.native.ts | 6 ++++++ packages/blade/src/utils/fireNativeEvent/index.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts new file mode 100644 index 00000000000..379458af0a1 --- /dev/null +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts @@ -0,0 +1,6 @@ +/** + * FireNativeEvent is not supported on react-native + */ +export const FireNativeEvent = (): void => { + throw new Error('FireNativeEvent is not supported on react-native'); +}; diff --git a/packages/blade/src/utils/fireNativeEvent/index.ts b/packages/blade/src/utils/fireNativeEvent/index.ts index 13d751eea57..945d5383130 100644 --- a/packages/blade/src/utils/fireNativeEvent/index.ts +++ b/packages/blade/src/utils/fireNativeEvent/index.ts @@ -1 +1 @@ -export * from './fireNativeEvent.web'; +export * from './fireNativeEvent'; From 4a5abdcd2c5985cf6d1234078f8d0316bad4299a Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 8 Nov 2024 15:55:39 +0530 Subject: [PATCH 14/27] chore: change fire native event --- .../utils/fireNativeEvent/fireNativeEvent.web.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts index d67eaa7ef44..26577674a25 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.ts @@ -19,12 +19,10 @@ export const fireNativeEvent = ( ref: React.RefObject | null, eventTypes: Array<'change' | 'input'>, ): void => { - if (ref) { - eventTypes.forEach((eventType) => { - const event = new Event(eventType, { bubbles: true }); - ref.current?.dispatchEvent(event); - }); - } else { - console.warn('ref is not defined'); - } + if (!ref) return; + + eventTypes.forEach((eventType) => { + const event = new Event(eventType, { bubbles: true }); + ref.current?.dispatchEvent(event); + }); }; From 2a8975ff25a97c65701158b2a63efa6bc9ac1fd3 Mon Sep 17 00:00:00 2001 From: tewarig Date: Sun, 10 Nov 2024 16:11:35 +0530 Subject: [PATCH 15/27] chore: review change useFireNativeEvent --- .../src/utils/fireNativeEvent/fireNativeEvent.native.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts index 379458af0a1..abfe0d5561b 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts @@ -1,6 +1,11 @@ +import { throwBladeError } from '../logger'; /** * FireNativeEvent is not supported on react-native */ + export const FireNativeEvent = (): void => { - throw new Error('FireNativeEvent is not supported on react-native'); + throwBladeError({ + message: 'FireNativeEvent is not supported on react-native', + moduleName: 'FireNativeEvent', + }); }; From c4887f65969f8672472e83595bf3ce2be9702bbf Mon Sep 17 00:00:00 2001 From: tewarig Date: Mon, 11 Nov 2024 17:08:48 +0530 Subject: [PATCH 16/27] feat: added fireNativeEvent test --- .../fireNativeEvent.web.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.test.ts diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.test.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.test.ts new file mode 100644 index 00000000000..9320da76fb3 --- /dev/null +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.web.test.ts @@ -0,0 +1,45 @@ +import React from 'react'; +import { fireNativeEvent } from './fireNativeEvent.web'; + +describe('fireNativeEvent', () => { + let ref: React.RefObject; + + beforeEach(() => { + ref = React.createRef(); + // using div as a test element because it don't have both "change" and "input" events + document.body.innerHTML = '
'; + Object.defineProperty(ref, 'current', { value: document.getElementById('test-element') }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should dispatch "change" event on the element', () => { + const handleChange = jest.fn(); + ref.current?.addEventListener('change', handleChange); + + fireNativeEvent(ref, ['change']); + expect(handleChange).toHaveBeenCalled(); + }); + + it('should dispatch "input" event on the element', () => { + const handleInput = jest.fn(); + ref.current?.addEventListener('input', handleInput); + + fireNativeEvent(ref, ['input']); + expect(handleInput).toHaveBeenCalled(); + }); + + it('should dispatch both "change" and "input" events on the element', () => { + const handleChange = jest.fn(); + const handleInput = jest.fn(); + + ref.current?.addEventListener('change', handleChange); + ref.current?.addEventListener('input', handleInput); + + fireNativeEvent(ref, ['change', 'input']); + expect(handleChange).toHaveBeenCalled(); + expect(handleInput).toHaveBeenCalled(); + }); +}); From 9295b8d0491e95ac3574fc19761509fdfed501d1 Mon Sep 17 00:00:00 2001 From: tewarig Date: Mon, 11 Nov 2024 17:27:58 +0530 Subject: [PATCH 17/27] chore: test for fireNativeEvent react native --- .../utils/fireNativeEvent/fireNativeEvent.native.test.ts | 7 +++++++ .../src/utils/fireNativeEvent/fireNativeEvent.native.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts new file mode 100644 index 00000000000..be75eb3a192 --- /dev/null +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts @@ -0,0 +1,7 @@ +import { fireNativeEvent } from './fireNativeEvent.native'; + +describe('fireNativeEvent', () => { + it('should throw error', () => { + expect(() => fireNativeEvent()).toThrowError(); + }); +}); diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts index abfe0d5561b..a0584553f56 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts @@ -3,7 +3,7 @@ import { throwBladeError } from '../logger'; * FireNativeEvent is not supported on react-native */ -export const FireNativeEvent = (): void => { +export const fireNativeEvent = (): void => { throwBladeError({ message: 'FireNativeEvent is not supported on react-native', moduleName: 'FireNativeEvent', From 7a6cfa92b292032edea099edfc04c22e1387e447 Mon Sep 17 00:00:00 2001 From: tewarig Date: Mon, 11 Nov 2024 18:02:26 +0530 Subject: [PATCH 18/27] chore: update fireNativeEvent error --- .../utils/fireNativeEvent/fireNativeEvent.native.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts index be75eb3a192..7a4a0415504 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts @@ -1,7 +1,9 @@ import { fireNativeEvent } from './fireNativeEvent.native'; describe('fireNativeEvent', () => { - it('should throw error', () => { - expect(() => fireNativeEvent()).toThrowError(); + it('should throw specific error', () => { + expect(() => fireNativeEvent()).toThrowError( + '[Blade: FireNativeEvent]: FireNativeEvent is not supported on react-native', + ); }); }); From f023d4cfc62d26157b21b32c2de5b85bec5e370c Mon Sep 17 00:00:00 2001 From: tewarig Date: Mon, 11 Nov 2024 20:59:32 +0530 Subject: [PATCH 19/27] chore: add tests in AutoComplete --- .../__tests__/AutoComplete.web.test.tsx | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/__tests__/AutoComplete.web.test.tsx b/packages/blade/src/components/Input/DropdownInputTriggers/__tests__/AutoComplete.web.test.tsx index 002351bb663..eb87167166b 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/__tests__/AutoComplete.web.test.tsx +++ b/packages/blade/src/components/Input/DropdownInputTriggers/__tests__/AutoComplete.web.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import userEvent from '@testing-library/user-event'; import { waitFor, screen, act } from '@testing-library/react'; import { Dropdown, DropdownOverlay } from '~components/Dropdown'; @@ -470,4 +470,122 @@ describe(' & with ', () => { await user.click(getByRole('option', { name: 'Pune' })); expect(getByRole('option', { name: 'Pune' })).toHaveAttribute('aria-selected', 'true'); }, 10000); + it('should fire native events like input and change', async () => { + const cities = [ + { + title: 'Mumbai', + value: 'mumbai', + keywords: ['maharashtra'], + }, + { + title: 'Pune', + value: 'pune', + keywords: ['maharashtra'], + }, + { + title: 'Bengaluru', + value: 'bengaluru', + keywords: ['karnataka', 'bangalore'], + }, + ]; + const handleInput = jest.fn(); + const handleChange = jest.fn(); + + const ControlledFiltering = (): React.ReactElement => { + const cityValues = cities.map((city) => city.value); + const [filteredValues, setFilteredValues] = React.useState(cityValues); + const ref = React.useRef(null); + + useEffect(() => { + if (ref?.current) { + ref.current.addEventListener('input', () => { + handleInput(); + }); + ref.current.addEventListener('change', () => { + handleChange(); + }); + } + return () => { + if (ref?.current) { + ref.current.removeEventListener('input', () => { + handleInput(); + }); + ref.current.removeEventListener('change', () => { + handleChange(); + }); + } + }; + }, []); + + return ( + + + + + + { + if (value) { + const filteredItems = cities + .filter( + (city) => + city.title.toLowerCase().startsWith(value.toLowerCase()) || + city.keywords.find((keyword) => + keyword.toLowerCase().includes(value.toLowerCase()), + ), + ) + .map((city) => city.value); + + if (filteredItems.length > 0) { + setFilteredValues(filteredItems); + } else { + setFilteredValues([]); + } + } else { + setFilteredValues(cityValues); + } + }} + filteredValues={filteredValues} + helpText="Try typing 'maharashtra' in input" + /> + + + + {cities.map((city) => ( + + ))} + + + + + + ); + }; + + const user = userEvent.setup(); + const { getByRole, getByLabelText, queryByTestId } = renderWithTheme(); + + const selectInput = getByLabelText('Cities') as HTMLInputElement; + const autoComplete = queryByTestId('cities-autocomplete') as HTMLInputElement; + + expect(selectInput).toBeInTheDocument(); + expect(queryByTestId('bottomsheet-body')).not.toBeVisible(); + + await user.click(selectInput); + await waitFor(() => expect(queryByTestId('bottomsheet-body')).toBeVisible()); + + act(() => { + autoComplete.focus(); + }); + + expect(getByRole('option', { name: 'Mumbai' })).toBeVisible(); + expect(getByRole('option', { name: 'Pune' })).toBeVisible(); + expect(getByRole('option', { name: 'Bengaluru' })).toBeVisible(); + + await user.click(getByRole('option', { name: 'Pune' })); + expect(handleInput).toBeCalled(); + expect(handleChange).toBeCalled(); + }, 10000); }); From 42b8c3db86048711bf140cfea176e9fdda43e2a6 Mon Sep 17 00:00:00 2001 From: tewarig Date: Mon, 11 Nov 2024 23:20:22 +0530 Subject: [PATCH 20/27] feat: add test for fire naitve event in DatePicker --- .../__tests__/DatePicker.web.test.tsx | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx diff --git a/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx b/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx new file mode 100644 index 00000000000..1fa71272dc3 --- /dev/null +++ b/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useRef } from 'react'; +import dayjs from 'dayjs'; +import userEvent from '@testing-library/user-event'; +import { waitFor } from '@testing-library/react'; +import { DatePicker as DatePickerComponent } from '..'; +import { Box } from '~components/Box'; +import renderWithTheme from '~utils/testing/renderWithTheme.web'; + +describe(' ', () => { + it('should fire native events like input and change', async () => { + const handleInput = jest.fn(); + const handleChange = jest.fn(); + + const DatePicker = (): React.ReactElement => { + const ref = useRef(null); + + const addEventListeners = (): void => { + if (ref.current) { + ref.current.addEventListener('input', handleInput); + ref.current.addEventListener('change', handleChange); + } + }; + + const removeEventListeners = (): void => { + if (ref.current) { + ref.current.removeEventListener('input', handleInput); + ref.current.removeEventListener('change', handleChange); + } + }; + + useEffect(() => { + addEventListeners(); + return removeEventListeners; + }, []); + + return ( + + + + ); + }; + + const user = userEvent.setup(); + const { getByRole, queryByText } = renderWithTheme(); + + const input = getByRole('combobox', { name: /Select Date/i }); + await user.click(input); + + await waitFor(() => expect(queryByText('Sun')).toBeVisible()); + + const dateToSelect = dayjs().add(1, 'day'); + const date = getByRole('button', { name: dateToSelect.format('DD MMMM YYYY') }); + await user.click(date); + + const applyButton = getByRole('button', { name: /Apply/i }); + await user.click(applyButton); + expect(handleChange).toBeCalled(); + expect(handleInput).toBeCalled(); + + await waitFor(() => { + expect(handleInput).toHaveBeenCalled(); + expect(handleChange).toHaveBeenCalled(); + }); + }); +}); From 5e517b39f4b24586cf253de5149729405dfc66a2 Mon Sep 17 00:00:00 2001 From: tewarig Date: Tue, 12 Nov 2024 00:09:46 +0530 Subject: [PATCH 21/27] feat: added test case for FileUpload --- .../__tests__/DatePicker.web.test.tsx | 5 -- .../__tests__/FileUpload.web.test.tsx | 56 ++++++++++++++++++- .../FileUpload.web.test.tsx.snap | 2 +- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx b/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx index 1fa71272dc3..daa8ceb9847 100644 --- a/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx +++ b/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx @@ -56,10 +56,5 @@ describe(' ', () => { await user.click(applyButton); expect(handleChange).toBeCalled(); expect(handleInput).toBeCalled(); - - await waitFor(() => { - expect(handleInput).toHaveBeenCalled(); - expect(handleChange).toHaveBeenCalled(); - }); }); }); diff --git a/packages/blade/src/components/FileUpload/__tests__/FileUpload.web.test.tsx b/packages/blade/src/components/FileUpload/__tests__/FileUpload.web.test.tsx index c9820708904..48839b3a1ac 100644 --- a/packages/blade/src/components/FileUpload/__tests__/FileUpload.web.test.tsx +++ b/packages/blade/src/components/FileUpload/__tests__/FileUpload.web.test.tsx @@ -1,5 +1,7 @@ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; +import userEvent from '@testing-library/user-event'; import { FileUpload } from '../FileUpload'; +import { Box } from '~components/Box'; import renderWithTheme from '~utils/testing/renderWithTheme.web'; import assertAccessible from '~utils/testing/assertAccessible.web'; @@ -102,4 +104,56 @@ describe('', () => { expect(getByTestId('file-upload-test')).toBeTruthy(); }); + it('Should fire native events like input and change', async () => { + const blob = new Blob(['']); + const filename = 'my-image.png'; + const file = new File([blob], filename, { + type: 'image/png', + }); + const user = userEvent.setup(); + const handleInput = jest.fn(); + const handleChange = jest.fn(); + + const DatePicker = (): React.ReactElement => { + const ref = useRef(null); + const addEventListeners = (): void => { + if (ref.current) { + ref.current.addEventListener('input', handleInput); + ref.current.addEventListener('change', handleChange); + } + }; + + const removeEventListeners = (): void => { + if (ref.current) { + ref.current.removeEventListener('input', handleInput); + ref.current.removeEventListener('change', handleChange); + } + }; + + useEffect(() => { + addEventListeners(); + return removeEventListeners; + }, []); + return ( + + + + ); + }; + const { getByText } = renderWithTheme(); + + const input = getByText('Drag files here or').closest('div')?.querySelector('input'); + + await user.upload(input as HTMLElement, file); + expect(getByText(filename)).toBeVisible(); + + expect(handleChange).toBeCalled(); + expect(handleInput).toBeCalled(); + }); }); diff --git a/packages/blade/src/components/FileUpload/__tests__/__snapshots__/FileUpload.web.test.tsx.snap b/packages/blade/src/components/FileUpload/__tests__/__snapshots__/FileUpload.web.test.tsx.snap index 9c748ed896b..cc634c22595 100644 --- a/packages/blade/src/components/FileUpload/__tests__/__snapshots__/FileUpload.web.test.tsx.snap +++ b/packages/blade/src/components/FileUpload/__tests__/__snapshots__/FileUpload.web.test.tsx.snap @@ -1307,7 +1307,7 @@ exports[` should set disabled state with isDisabled 1`] = ` } .c15.c15.c15.c15.c15 { - color: hsla(227,100%,59%,0.18); + color: hsla(227,100%,59%,0.32); font-family: "Inter","Inter Fallback Arial",Arial; font-size: 0.875rem; font-weight: 400; From 500e30f8ca189bab6de26b516057be5264716ff5 Mon Sep 17 00:00:00 2001 From: tewarig Date: Thu, 14 Nov 2024 11:23:23 +0530 Subject: [PATCH 22/27] fix: failing build --- .../src/utils/fireNativeEvent/fireNativeEvent.native.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts index a0584553f56..c91d5cf9394 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts @@ -3,7 +3,10 @@ import { throwBladeError } from '../logger'; * FireNativeEvent is not supported on react-native */ -export const fireNativeEvent = (): void => { +export const fireNativeEvent = ( + _ref: React.RefObject | null, + _eventTypes: Array<'change' | 'input'>, +): void => { throwBladeError({ message: 'FireNativeEvent is not supported on react-native', moduleName: 'FireNativeEvent', From 2e3b148d34bc06c89227fda5da8e41a058465c87 Mon Sep 17 00:00:00 2001 From: tewarig Date: Thu, 14 Nov 2024 11:40:55 +0530 Subject: [PATCH 23/27] chore: update snap --- .../__tests__/__snapshots__/FileUpload.web.test.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blade/src/components/FileUpload/__tests__/__snapshots__/FileUpload.web.test.tsx.snap b/packages/blade/src/components/FileUpload/__tests__/__snapshots__/FileUpload.web.test.tsx.snap index cc634c22595..9c748ed896b 100644 --- a/packages/blade/src/components/FileUpload/__tests__/__snapshots__/FileUpload.web.test.tsx.snap +++ b/packages/blade/src/components/FileUpload/__tests__/__snapshots__/FileUpload.web.test.tsx.snap @@ -1307,7 +1307,7 @@ exports[` should set disabled state with isDisabled 1`] = ` } .c15.c15.c15.c15.c15 { - color: hsla(227,100%,59%,0.32); + color: hsla(227,100%,59%,0.18); font-family: "Inter","Inter Fallback Arial",Arial; font-size: 0.875rem; font-weight: 400; From 277c1b964e742fa2e203563e722450d290ad5e32 Mon Sep 17 00:00:00 2001 From: tewarig Date: Thu, 14 Nov 2024 12:30:48 +0530 Subject: [PATCH 24/27] chore: add fixes to fireNativeEvent --- .../src/utils/fireNativeEvent/fireNativeEvent.native.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts index 7a4a0415504..01d1482a13a 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts @@ -1,8 +1,9 @@ import { fireNativeEvent } from './fireNativeEvent.native'; describe('fireNativeEvent', () => { + const ref: React.RefObject = { current: null }; it('should throw specific error', () => { - expect(() => fireNativeEvent()).toThrowError( + expect(() => fireNativeEvent(ref, ['change'])).toThrowError( '[Blade: FireNativeEvent]: FireNativeEvent is not supported on react-native', ); }); From d6947b0d1a807bbd3c72a4e34c957eaa4e9e6787 Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 15 Nov 2024 16:40:59 +0530 Subject: [PATCH 25/27] chore: change throwBladeError to logger --- .../fireNativeEvent/fireNativeEvent.native.test.ts | 10 ++++++++-- .../utils/fireNativeEvent/fireNativeEvent.native.ts | 5 +++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts index 01d1482a13a..c5dafc1ffcb 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts @@ -2,9 +2,15 @@ import { fireNativeEvent } from './fireNativeEvent.native'; describe('fireNativeEvent', () => { const ref: React.RefObject = { current: null }; - it('should throw specific error', () => { - expect(() => fireNativeEvent(ref, ['change'])).toThrowError( + it('should log specific error', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + fireNativeEvent(ref, ['change']); + + expect(consoleErrorSpy).toHaveBeenCalledWith( '[Blade: FireNativeEvent]: FireNativeEvent is not supported on react-native', ); + + consoleErrorSpy.mockRestore(); }); }); diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts index c91d5cf9394..46a9a9ef8c2 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts @@ -1,4 +1,4 @@ -import { throwBladeError } from '../logger'; +import { logger } from '../logger'; /** * FireNativeEvent is not supported on react-native */ @@ -7,8 +7,9 @@ export const fireNativeEvent = ( _ref: React.RefObject | null, _eventTypes: Array<'change' | 'input'>, ): void => { - throwBladeError({ + logger({ message: 'FireNativeEvent is not supported on react-native', moduleName: 'FireNativeEvent', + type: 'error', }); }; From f0d49f54554a82db78f48b3cc2d9b5b8349b13d1 Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 15 Nov 2024 17:06:28 +0530 Subject: [PATCH 26/27] chore: fix errors --- packages/blade/src/components/Dropdown/useDropdown.ts | 6 ++++-- .../DropdownInputTriggers/BaseDropdownInputTrigger.tsx | 6 ++++-- .../fireNativeEvent/fireNativeEvent.native.test.ts | 10 ++-------- .../utils/fireNativeEvent/fireNativeEvent.native.ts | 5 ++--- packages/blade/src/utils/platform/index.all.ts | 1 + packages/blade/src/utils/platform/index.native.ts | 1 + packages/blade/src/utils/platform/index.ts | 1 + packages/blade/src/utils/platform/isBrowser.ts | 7 +++++++ 8 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 packages/blade/src/utils/platform/isBrowser.ts diff --git a/packages/blade/src/components/Dropdown/useDropdown.ts b/packages/blade/src/components/Dropdown/useDropdown.ts index 806db93dac3..8841cc16860 100644 --- a/packages/blade/src/components/Dropdown/useDropdown.ts +++ b/packages/blade/src/components/Dropdown/useDropdown.ts @@ -14,7 +14,7 @@ import type { DropdownProps } from './types'; import { dropdownComponentIds } from './dropdownComponentIds'; import type { FormInputHandleOnKeyDownEvent } from '~components/Form/FormTypes'; -import { isReactNative } from '~utils'; +import { isReactNative, isBrowser } from '~utils'; import type { ContainerElementType } from '~utils/types'; import { fireNativeEvent } from '~utils/fireNativeEvent'; @@ -356,7 +356,9 @@ const useDropdown = (): UseDropdownReturnValue => { const optionValues = options.map((option) => option.value); ensureScrollVisiblity(updatedIndex, rest.actionListItemRef.current, optionValues); - fireNativeEvent(rest.actionListItemRef as React.RefObject, ['change', 'input']); + if (isBrowser()) { + fireNativeEvent(rest.actionListItemRef as React.RefObject, ['change', 'input']); + } }; /** diff --git a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx index 4344441698b..ece946ddc09 100644 --- a/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx +++ b/packages/blade/src/components/Input/DropdownInputTriggers/BaseDropdownInputTrigger.tsx @@ -5,7 +5,7 @@ import { InputChevronIcon } from './InputChevronIcon'; import type { BaseDropdownInputTriggerProps, useControlledDropdownInputProps } from './types'; import isEmpty from '~utils/lodashButBetter/isEmpty'; import { useDropdown } from '~components/Dropdown/useDropdown'; -import { isReactNative } from '~utils'; +import { isReactNative, isBrowser } from '~utils'; import { getActionListContainerRole } from '~components/ActionList/getA11yRoles'; import { MetaConstants } from '~utils/metaAttribute'; import { getTagsGroup } from '~components/Tag/getTagsGroup'; @@ -106,7 +106,9 @@ const useControlledDropdownInput = (props: useControlledDropdownInputProps): voi name: props.name, values: getValuesArrayFromIndices(), }); - fireNativeEvent(props.triggererRef, ['change', 'input']); + if (isBrowser()) { + fireNativeEvent(props.triggererRef, ['change', 'input']); + } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [changeCallbackTriggerer]); diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts index c5dafc1ffcb..01d1482a13a 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.test.ts @@ -2,15 +2,9 @@ import { fireNativeEvent } from './fireNativeEvent.native'; describe('fireNativeEvent', () => { const ref: React.RefObject = { current: null }; - it('should log specific error', () => { - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - - fireNativeEvent(ref, ['change']); - - expect(consoleErrorSpy).toHaveBeenCalledWith( + it('should throw specific error', () => { + expect(() => fireNativeEvent(ref, ['change'])).toThrowError( '[Blade: FireNativeEvent]: FireNativeEvent is not supported on react-native', ); - - consoleErrorSpy.mockRestore(); }); }); diff --git a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts index 46a9a9ef8c2..c91d5cf9394 100644 --- a/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts +++ b/packages/blade/src/utils/fireNativeEvent/fireNativeEvent.native.ts @@ -1,4 +1,4 @@ -import { logger } from '../logger'; +import { throwBladeError } from '../logger'; /** * FireNativeEvent is not supported on react-native */ @@ -7,9 +7,8 @@ export const fireNativeEvent = ( _ref: React.RefObject | null, _eventTypes: Array<'change' | 'input'>, ): void => { - logger({ + throwBladeError({ message: 'FireNativeEvent is not supported on react-native', moduleName: 'FireNativeEvent', - type: 'error', }); }; diff --git a/packages/blade/src/utils/platform/index.all.ts b/packages/blade/src/utils/platform/index.all.ts index 640f5651e48..ab3dd132729 100644 --- a/packages/blade/src/utils/platform/index.all.ts +++ b/packages/blade/src/utils/platform/index.all.ts @@ -1,4 +1,5 @@ export { isReactNative } from './isReactNative'; +export { isBrowser } from './isBrowser'; export * from './getOS'; export type { Platform } from './platform.all'; export * from './castUtils'; diff --git a/packages/blade/src/utils/platform/index.native.ts b/packages/blade/src/utils/platform/index.native.ts index e8f448c3136..2e04d2600a3 100644 --- a/packages/blade/src/utils/platform/index.native.ts +++ b/packages/blade/src/utils/platform/index.native.ts @@ -1,4 +1,5 @@ export { isReactNative } from './isReactNative'; +export { isBrowser } from './isBrowser'; export * from './getOS'; export type { Platform } from './platform.native'; export * from './castUtils'; diff --git a/packages/blade/src/utils/platform/index.ts b/packages/blade/src/utils/platform/index.ts index 95171c55cbc..1248971bad2 100644 --- a/packages/blade/src/utils/platform/index.ts +++ b/packages/blade/src/utils/platform/index.ts @@ -1,4 +1,5 @@ export { isReactNative } from './isReactNative'; +export { isBrowser } from './isBrowser'; export * from './getOS'; export type { Platform } from './platform'; export * from './castUtils'; diff --git a/packages/blade/src/utils/platform/isBrowser.ts b/packages/blade/src/utils/platform/isBrowser.ts new file mode 100644 index 00000000000..993d7ce1b3f --- /dev/null +++ b/packages/blade/src/utils/platform/isBrowser.ts @@ -0,0 +1,7 @@ +import { getPlatformType } from '../getPlatformType'; + +const isBrowser = (): boolean => { + return getPlatformType() === 'browser'; +}; + +export { isBrowser }; From 82104ef45a1f8c641d123b5705cf8537a244e7cb Mon Sep 17 00:00:00 2001 From: tewarig Date: Fri, 15 Nov 2024 17:39:51 +0530 Subject: [PATCH 27/27] chore: increase timeout --- .../src/components/DatePicker/__tests__/DatePicker.web.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx b/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx index daa8ceb9847..4862c74ce74 100644 --- a/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx +++ b/packages/blade/src/components/DatePicker/__tests__/DatePicker.web.test.tsx @@ -7,6 +7,7 @@ import { Box } from '~components/Box'; import renderWithTheme from '~utils/testing/renderWithTheme.web'; describe(' ', () => { + jest.setTimeout(10000); it('should fire native events like input and change', async () => { const handleInput = jest.fn(); const handleChange = jest.fn();