Skip to content

Commit 401ca18

Browse files
authored
refactor(enrichEventWithDetails): pass through detail, improve types (#5984)
1 parent 5bcccbb commit 401ca18

File tree

8 files changed

+120
-62
lines changed

8 files changed

+120
-62
lines changed

packages/base/src/utils/index.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { UIEvent } from 'react';
1+
import type { SyntheticEvent } from 'react';
22

33
export const deprecationNotice = (component: string, message: string) => {
44
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
@@ -11,28 +11,46 @@ export const deprecationNotice = (component: string, message: string) => {
1111
}
1212
};
1313

14-
export const enrichEventWithDetails = <T extends Record<string, unknown>, ReturnType = CustomEvent<T>>(
15-
event: UIEvent,
16-
payload: T | null = null
17-
) => {
18-
// safeguard
19-
if (!event) {
20-
return event;
21-
}
22-
if (event.hasOwnProperty('persist')) {
23-
// if there is a persist method, it's an SyntheticEvent so we need to persist it
24-
event.persist();
14+
// Define EnrichedEventType to extend Event with specific detail structure
15+
type EnrichedEventType<Event, Detail> = Event & {
16+
detail: Detail & { nativeDetail?: number };
17+
};
18+
19+
export const enrichEventWithDetails = <
20+
Event extends { detail?: number | Record<string, unknown> | null },
21+
Detail extends Record<string, unknown>
22+
>(
23+
event: Event,
24+
payload: Detail
25+
): EnrichedEventType<Event, Detail> => {
26+
// todo: once we drop React 16 support, remove this
27+
// the helper accepts both SyntheticEvents and browser events
28+
const syntheticEventCast = event as unknown as SyntheticEvent;
29+
if (typeof syntheticEventCast.persist === 'function') {
30+
// if there is a persist method, it's a SyntheticEvent so we need to persist it
31+
syntheticEventCast.persist();
2532
}
2633

34+
// Determine if we need to create a new details object
2735
const shouldCreateNewDetails =
2836
event.detail === null || event.detail === undefined || typeof event.detail !== 'object';
29-
Object.defineProperty(event, 'detail', {
37+
38+
// native detail is always number (if available)
39+
const nativeDetail = typeof event.detail === 'number' ? event.detail : null;
40+
41+
// defineProperty is necessary here as event.detail needs to be editable
42+
Object.defineProperty<Event>(event, 'detail', {
3043
value: shouldCreateNewDetails ? {} : event.detail,
3144
writable: true,
3245
configurable: true
3346
});
34-
Object.assign(event.detail, payload);
35-
return event as unknown as ReturnType;
47+
48+
if (nativeDetail) {
49+
Object.assign(event.detail!, { nativeDetail });
50+
}
51+
Object.assign(event.detail!, payload);
52+
53+
return event as EnrichedEventType<Event, Detail>;
3654
};
3755

3856
export { debounce } from './debounce.js';

packages/main/src/components/AnalyticalTable/types/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,11 @@ export interface AnalyticalTableDomRef extends Omit<HTMLDivElement, 'scrollTo'>
344344
type HighlightColor = ValueState | keyof typeof ValueState | IndicationColor | keyof typeof IndicationColor;
345345

346346
interface OnAutoResizeMouseEvent extends Omit<MouseEvent, 'detail'> {
347-
detail: { columnId: string; width: number };
347+
detail: { columnId: string; width: number; nativeDetail: 2 };
348+
}
349+
350+
interface OnRowClickEvent extends Omit<MouseEvent, 'detail'> {
351+
detail: { detail: { row: unknown; nativeDetail: number } };
348352
}
349353

350354
export interface AnalyticalTablePropTypes extends Omit<CommonProps, 'title'> {
@@ -664,12 +668,13 @@ export interface AnalyticalTablePropTypes extends Omit<CommonProps, 'title'> {
664668
isSelected?: boolean;
665669
selectedFlatRows: Record<string, unknown>[];
666670
selectedRowIds: Record<string | number, boolean>;
671+
nativeDetail: number;
667672
}>
668673
) => void;
669674
/**
670675
* Fired when a row is clicked
671676
*/
672-
onRowClick?: (e?: CustomEvent<{ row?: unknown }>) => void;
677+
onRowClick?: OnRowClickEvent;
673678
/**
674679
* Fired when a row is expanded or collapsed
675680
*/

packages/main/src/components/FilterBar/FilterDialog.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { FilterBarDialogContext } from '../../internal/FilterBarDialogContext.js
4242
import { useCanRenderPortal } from '../../internal/ssr.js';
4343
import { stopPropagation } from '../../internal/stopPropagation.js';
4444
import type { Ui5CustomEvent } from '../../types/index.js';
45-
import type { DialogDomRef, SegmentedButtonPropTypes, TableDomRef, TableRowDomRef } from '../../webComponents/index.js';
45+
import type { DialogDomRef, SegmentedButtonPropTypes, TableRowDomRef } from '../../webComponents/index.js';
4646
import {
4747
Bar,
4848
Button,
@@ -122,12 +122,7 @@ interface FilterDialogPropTypes {
122122
handleRestoreFilters: (e, source, filterElements) => void;
123123
handleDialogSave: (e, newRefs, updatedToggledFilters, orderedChildren) => void;
124124
handleSearchValueChange: Dispatch<SetStateAction<string>>;
125-
handleSelectionChange?: (
126-
event: Ui5CustomEvent<
127-
TableDomRef,
128-
{ element: TableRowDomRef; checked: boolean; selectedRows: unknown[]; previouslySelectedRows: unknown[] }
129-
>
130-
) => void;
125+
handleSelectionChange?: FilterBarPropTypes['onFiltersDialogSelectionChange'];
131126
handleDialogSearch?: (event: CustomEvent<{ value: string; element: HTMLElement }>) => void;
132127
handleDialogCancel?: (event: Ui5CustomEvent<HTMLElement>) => void;
133128
portalContainer: Element;
@@ -266,7 +261,7 @@ export const FilterDialog = (props: FilterDialogPropTypes) => {
266261
};
267262

268263
const handleSearch = (e) => {
269-
if (handleDialogSearch) {
264+
if (typeof handleDialogSearch === 'function') {
270265
handleDialogSearch(enrichEventWithDetails(e, { value: e.target.value, element: e.target }));
271266
}
272267
setSearchString(e.target.value);
@@ -279,7 +274,7 @@ export const FilterDialog = (props: FilterDialogPropTypes) => {
279274
const handleClose = (e) => {
280275
setToggledFilters({});
281276
stopPropagation(e);
282-
if (handleDialogCancel) {
277+
if (typeof handleDialogCancel === 'function') {
283278
handleDialogCancel(e);
284279
}
285280
handleDialogClose(e);

packages/main/src/components/FilterBar/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ const FilterBar = forwardRef<HTMLDivElement, FilterBarPropTypes>((props, ref) =>
163163
};
164164

165165
const handleToggle = (e) => {
166-
if (onToggleFilters) {
166+
if (typeof onToggleFilters === 'function') {
167167
onToggleFilters(enrichEventWithDetails(e, { visible: !showFilters, ...getFilterElements() }));
168168
}
169169
setShowFilters(!showFilters);
@@ -198,7 +198,7 @@ const FilterBar = forwardRef<HTMLDivElement, FilterBarPropTypes>((props, ref) =>
198198

199199
const handleDialogClose = (e) => {
200200
if (onFiltersDialogClose) {
201-
onFiltersDialogClose(enrichEventWithDetails(e));
201+
onFiltersDialogClose(e);
202202
}
203203
setDialogOpen(false);
204204
void filterBtnRef.current?.focus();

packages/main/src/components/FilterBar/types.ts

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,38 @@
1-
import type { CSSProperties, ReactElement, ReactNode } from 'react';
1+
import type { CSSProperties, MouseEvent, ReactElement, ReactNode } from 'react';
22
import type { CommonProps, Ui5CustomEvent } from '../../types/index.js';
3-
import type { DialogDomRef, InputPropTypes, TableDomRef, TableRowDomRef } from '../../webComponents/index.js';
3+
import type {
4+
ButtonPropTypes,
5+
DialogPropTypes,
6+
InputPropTypes,
7+
TableRowDomRef,
8+
TableSelectionDomRef
9+
} from '../../webComponents/index.js';
410
import type { FilterGroupItemInternalProps } from '../FilterGroupItem/types.js';
511

12+
interface OnToggleFiltersEvent extends Omit<MouseEvent, 'detail'> {
13+
detail: { visible: boolean; filters: HTMLElement[]; search: HTMLElement; nativeDetail: number };
14+
}
15+
16+
interface OnFiltersDialogSaveEvent extends Omit<MouseEvent, 'detail'> {
17+
detail: {
18+
elements: Record<string, HTMLElement>;
19+
toggledElements?: Record<string, HTMLElement>;
20+
filters: HTMLElement[];
21+
search: HTMLElement;
22+
orderIds: string[];
23+
nativeDetail: number;
24+
};
25+
}
26+
27+
interface OnGoEvent extends Omit<MouseEvent, 'detail'> {
28+
detail: {
29+
elements: Record<string, HTMLElement>;
30+
filters: HTMLElement[];
31+
search: HTMLElement;
32+
nativeDetail: number;
33+
};
34+
}
35+
636
export interface FilterBarPropTypes extends CommonProps {
737
/**
838
* Defines the filters of the `FilterBar`.
@@ -103,19 +133,11 @@ export interface FilterBarPropTypes extends CommonProps {
103133
/**
104134
* The event is fired when the `FilterBar` is collapsed/expanded.
105135
*/
106-
onToggleFilters?: (event: CustomEvent<{ visible: boolean; filters: HTMLElement[]; search: HTMLElement }>) => void;
136+
onToggleFilters?: (event: OnToggleFiltersEvent) => void;
107137
/**
108138
* The event is fired when the "Go" button of the filter configuration dialog is clicked.
109139
*/
110-
onFiltersDialogSave?: (
111-
event: CustomEvent<{
112-
elements: Record<string, HTMLElement>;
113-
toggledElements?: Record<string, HTMLElement>;
114-
filters: HTMLElement[];
115-
search: HTMLElement;
116-
orderIds: string[];
117-
}>
118-
) => void;
140+
onFiltersDialogSave?: (event: OnFiltersDialogSaveEvent) => void;
119141
/**
120142
* The event is fired when the "Cancel" button of the filter configuration dialog is clicked or when the dialog is closed by pressing the "Escape" key.
121143
*/
@@ -125,11 +147,11 @@ export interface FilterBarPropTypes extends CommonProps {
125147
*
126148
* __Note:__ By adding `event.preventDefault()` to the function body, opening the dialog is prevented and you can add your own custom component. Even though this is possible, we highly recommend using the default dialog in order to preserve the intended design.
127149
*/
128-
onFiltersDialogOpen?: (event: CustomEvent) => void;
150+
onFiltersDialogOpen?: ButtonPropTypes['onClick'];
129151
/**
130152
* The event is fired after the filter configuration dialog has been opened.
131153
*/
132-
onAfterFiltersDialogOpen?: (event: Ui5CustomEvent<DialogDomRef>) => void;
154+
onAfterFiltersDialogOpen?: DialogPropTypes['onOpen'];
133155
/**
134156
* The event is fired when the filter configuration dialog is closed.
135157
*/
@@ -139,7 +161,7 @@ export interface FilterBarPropTypes extends CommonProps {
139161
*/
140162
onFiltersDialogSelectionChange?: (
141163
event: Ui5CustomEvent<
142-
TableDomRef,
164+
TableSelectionDomRef,
143165
{ element: TableRowDomRef; checked: boolean; selectedRows: unknown[]; previouslySelectedRows: unknown[] }
144166
>
145167
) => void;
@@ -154,18 +176,17 @@ export interface FilterBarPropTypes extends CommonProps {
154176
/**
155177
* The event is fired when the "Go" button is clicked.
156178
*/
157-
onGo?: (
158-
event: CustomEvent<{
159-
elements: Record<string, HTMLElement>;
160-
filters: HTMLElement[];
161-
search: HTMLElement;
162-
}>
163-
) => void;
179+
onGo?: (event: OnGoEvent) => void;
164180
/**
165181
* The event is fired when the "Reset" button is clicked.
166182
*/
167183
onRestore?: (
168-
event: CustomEvent<{ source: string; filters: HTMLElement[] | TableRowDomRef[]; search?: HTMLElement }>
184+
event: CustomEvent<{
185+
source: string;
186+
filters: HTMLElement[] | TableRowDomRef[];
187+
search?: HTMLElement;
188+
nativeDetail?: number;
189+
}>
169190
) => void;
170191
}
171192

packages/main/src/components/SelectDialog/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,13 @@ export interface SelectDialogPropTypes
110110
/**
111111
* This event will be fired when the reset button has been clicked in the search field or when the dialog is closed.
112112
*/
113-
onSearchReset?: (event: Ui5CustomEvent<IconDomRef, { prevValue: string }>) => void;
113+
onSearchReset?: (event: Ui5CustomEvent<{ prevValue: string; nativeDetail?: number }>) => void;
114114
/**
115115
* This event will be fired when the clear button has been clicked.
116116
*/
117-
onClear?: (event: Ui5CustomEvent<ButtonDomRef, { prevSelectedItems: ListItemStandardDomRef[] }>) => void;
117+
onClear?: (
118+
event: Ui5CustomEvent<ButtonDomRef, { prevSelectedItems: ListItemStandardDomRef[]; nativeDetail: number }>
119+
) => void;
118120
/**
119121
* This event will be fired when the dialog is confirmed by selecting an item in single selection mode or by pressing the confirmation button in multi selection mode.
120122
*/

0 commit comments

Comments
 (0)