Skip to content

refactor(enrichEventWithDetails): pass through detail, improve types #5984

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 28, 2024
Merged
48 changes: 33 additions & 15 deletions packages/base/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { UIEvent } from 'react';
import type { SyntheticEvent } from 'react';

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

export const enrichEventWithDetails = <T extends Record<string, unknown>, ReturnType = CustomEvent<T>>(
event: UIEvent,
payload: T | null = null
) => {
// safeguard
if (!event) {
return event;
}
if (event.hasOwnProperty('persist')) {
// if there is a persist method, it's an SyntheticEvent so we need to persist it
event.persist();
// Define EnrichedEventType to extend Event with specific detail structure
type EnrichedEventType<Event, Detail> = Event & {
detail: Detail & { nativeDetail?: number };
};

export const enrichEventWithDetails = <
Event extends { detail?: number | Record<string, unknown> | null },
Detail extends Record<string, unknown>
>(
event: Event,
payload: Detail
): EnrichedEventType<Event, Detail> => {
// todo: once we drop React 16 support, remove this
// the helper accepts both SyntheticEvents and browser events
const syntheticEventCast = event as unknown as SyntheticEvent;
if (typeof syntheticEventCast.persist === 'function') {
// if there is a persist method, it's a SyntheticEvent so we need to persist it
syntheticEventCast.persist();
}

// Determine if we need to create a new details object
const shouldCreateNewDetails =
event.detail === null || event.detail === undefined || typeof event.detail !== 'object';
Object.defineProperty(event, 'detail', {

// native detail is always number (if available)
const nativeDetail = typeof event.detail === 'number' ? event.detail : null;

// defineProperty is necessary here as event.detail needs to be editable
Object.defineProperty<Event>(event, 'detail', {
value: shouldCreateNewDetails ? {} : event.detail,
writable: true,
configurable: true
});
Object.assign(event.detail, payload);
return event as unknown as ReturnType;

if (nativeDetail) {
Object.assign(event.detail!, { nativeDetail });
}
Object.assign(event.detail!, payload);

return event as EnrichedEventType<Event, Detail>;
};

export { debounce } from './debounce.js';
Expand Down
9 changes: 7 additions & 2 deletions packages/main/src/components/AnalyticalTable/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,11 @@ export interface AnalyticalTableDomRef extends Omit<HTMLDivElement, 'scrollTo'>
type HighlightColor = ValueState | keyof typeof ValueState | IndicationColor | keyof typeof IndicationColor;

interface OnAutoResizeMouseEvent extends Omit<MouseEvent, 'detail'> {
detail: { columnId: string; width: number };
detail: { columnId: string; width: number; nativeDetail: 2 };
}

interface OnRowClickEvent extends Omit<MouseEvent, 'detail'> {
detail: { detail: { row: unknown; nativeDetail: number } };
}

export interface AnalyticalTablePropTypes extends Omit<CommonProps, 'title'> {
Expand Down Expand Up @@ -664,12 +668,13 @@ export interface AnalyticalTablePropTypes extends Omit<CommonProps, 'title'> {
isSelected?: boolean;
selectedFlatRows: Record<string, unknown>[];
selectedRowIds: Record<string | number, boolean>;
nativeDetail: number;
}>
) => void;
/**
* Fired when a row is clicked
*/
onRowClick?: (e?: CustomEvent<{ row?: unknown }>) => void;
onRowClick?: OnRowClickEvent;
/**
* Fired when a row is expanded or collapsed
*/
Expand Down
13 changes: 4 additions & 9 deletions packages/main/src/components/FilterBar/FilterDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { FilterBarDialogContext } from '../../internal/FilterBarDialogContext.js
import { useCanRenderPortal } from '../../internal/ssr.js';
import { stopPropagation } from '../../internal/stopPropagation.js';
import type { Ui5CustomEvent } from '../../types/index.js';
import type { DialogDomRef, SegmentedButtonPropTypes, TableDomRef, TableRowDomRef } from '../../webComponents/index.js';
import type { DialogDomRef, SegmentedButtonPropTypes, TableRowDomRef } from '../../webComponents/index.js';
import {
Bar,
Button,
Expand Down Expand Up @@ -122,12 +122,7 @@ interface FilterDialogPropTypes {
handleRestoreFilters: (e, source, filterElements) => void;
handleDialogSave: (e, newRefs, updatedToggledFilters, orderedChildren) => void;
handleSearchValueChange: Dispatch<SetStateAction<string>>;
handleSelectionChange?: (
event: Ui5CustomEvent<
TableDomRef,
{ element: TableRowDomRef; checked: boolean; selectedRows: unknown[]; previouslySelectedRows: unknown[] }
>
) => void;
handleSelectionChange?: FilterBarPropTypes['onFiltersDialogSelectionChange'];
handleDialogSearch?: (event: CustomEvent<{ value: string; element: HTMLElement }>) => void;
handleDialogCancel?: (event: Ui5CustomEvent<HTMLElement>) => void;
portalContainer: Element;
Expand Down Expand Up @@ -266,7 +261,7 @@ export const FilterDialog = (props: FilterDialogPropTypes) => {
};

const handleSearch = (e) => {
if (handleDialogSearch) {
if (typeof handleDialogSearch === 'function') {
handleDialogSearch(enrichEventWithDetails(e, { value: e.target.value, element: e.target }));
}
setSearchString(e.target.value);
Expand All @@ -279,7 +274,7 @@ export const FilterDialog = (props: FilterDialogPropTypes) => {
const handleClose = (e) => {
setToggledFilters({});
stopPropagation(e);
if (handleDialogCancel) {
if (typeof handleDialogCancel === 'function') {
handleDialogCancel(e);
}
handleDialogClose(e);
Expand Down
4 changes: 2 additions & 2 deletions packages/main/src/components/FilterBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ const FilterBar = forwardRef<HTMLDivElement, FilterBarPropTypes>((props, ref) =>
};

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

const handleDialogClose = (e) => {
if (onFiltersDialogClose) {
onFiltersDialogClose(enrichEventWithDetails(e));
onFiltersDialogClose(e);
}
setDialogOpen(false);
void filterBtnRef.current?.focus();
Expand Down
67 changes: 44 additions & 23 deletions packages/main/src/components/FilterBar/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
import type { CSSProperties, ReactElement, ReactNode } from 'react';
import type { CSSProperties, MouseEvent, ReactElement, ReactNode } from 'react';
import type { CommonProps, Ui5CustomEvent } from '../../types/index.js';
import type { DialogDomRef, InputPropTypes, TableDomRef, TableRowDomRef } from '../../webComponents/index.js';
import type {
ButtonPropTypes,
DialogPropTypes,
InputPropTypes,
TableRowDomRef,
TableSelectionDomRef
} from '../../webComponents/index.js';
import type { FilterGroupItemInternalProps } from '../FilterGroupItem/types.js';

interface OnToggleFiltersEvent extends Omit<MouseEvent, 'detail'> {
detail: { visible: boolean; filters: HTMLElement[]; search: HTMLElement; nativeDetail: number };
}

interface OnFiltersDialogSaveEvent extends Omit<MouseEvent, 'detail'> {
detail: {
elements: Record<string, HTMLElement>;
toggledElements?: Record<string, HTMLElement>;
filters: HTMLElement[];
search: HTMLElement;
orderIds: string[];
nativeDetail: number;
};
}

interface OnGoEvent extends Omit<MouseEvent, 'detail'> {
detail: {
elements: Record<string, HTMLElement>;
filters: HTMLElement[];
search: HTMLElement;
nativeDetail: number;
};
}

export interface FilterBarPropTypes extends CommonProps {
/**
* Defines the filters of the `FilterBar`.
Expand Down Expand Up @@ -103,19 +133,11 @@ export interface FilterBarPropTypes extends CommonProps {
/**
* The event is fired when the `FilterBar` is collapsed/expanded.
*/
onToggleFilters?: (event: CustomEvent<{ visible: boolean; filters: HTMLElement[]; search: HTMLElement }>) => void;
onToggleFilters?: (event: OnToggleFiltersEvent) => void;
/**
* The event is fired when the "Go" button of the filter configuration dialog is clicked.
*/
onFiltersDialogSave?: (
event: CustomEvent<{
elements: Record<string, HTMLElement>;
toggledElements?: Record<string, HTMLElement>;
filters: HTMLElement[];
search: HTMLElement;
orderIds: string[];
}>
) => void;
onFiltersDialogSave?: (event: OnFiltersDialogSaveEvent) => void;
/**
* 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.
*/
Expand All @@ -125,11 +147,11 @@ export interface FilterBarPropTypes extends CommonProps {
*
* __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.
*/
onFiltersDialogOpen?: (event: CustomEvent) => void;
onFiltersDialogOpen?: ButtonPropTypes['onClick'];
/**
* The event is fired after the filter configuration dialog has been opened.
*/
onAfterFiltersDialogOpen?: (event: Ui5CustomEvent<DialogDomRef>) => void;
onAfterFiltersDialogOpen?: DialogPropTypes['onOpen'];
/**
* The event is fired when the filter configuration dialog is closed.
*/
Expand All @@ -139,7 +161,7 @@ export interface FilterBarPropTypes extends CommonProps {
*/
onFiltersDialogSelectionChange?: (
event: Ui5CustomEvent<
TableDomRef,
TableSelectionDomRef,
{ element: TableRowDomRef; checked: boolean; selectedRows: unknown[]; previouslySelectedRows: unknown[] }
>
) => void;
Expand All @@ -154,18 +176,17 @@ export interface FilterBarPropTypes extends CommonProps {
/**
* The event is fired when the "Go" button is clicked.
*/
onGo?: (
event: CustomEvent<{
elements: Record<string, HTMLElement>;
filters: HTMLElement[];
search: HTMLElement;
}>
) => void;
onGo?: (event: OnGoEvent) => void;
/**
* The event is fired when the "Reset" button is clicked.
*/
onRestore?: (
event: CustomEvent<{ source: string; filters: HTMLElement[] | TableRowDomRef[]; search?: HTMLElement }>
event: CustomEvent<{
source: string;
filters: HTMLElement[] | TableRowDomRef[];
search?: HTMLElement;
nativeDetail?: number;
}>
) => void;
}

Expand Down
6 changes: 4 additions & 2 deletions packages/main/src/components/SelectDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,13 @@ export interface SelectDialogPropTypes
/**
* This event will be fired when the reset button has been clicked in the search field or when the dialog is closed.
*/
onSearchReset?: (event: Ui5CustomEvent<IconDomRef, { prevValue: string }>) => void;
onSearchReset?: (event: Ui5CustomEvent<{ prevValue: string; nativeDetail?: number }>) => void;
/**
* This event will be fired when the clear button has been clicked.
*/
onClear?: (event: Ui5CustomEvent<ButtonDomRef, { prevSelectedItems: ListItemStandardDomRef[] }>) => void;
onClear?: (
event: Ui5CustomEvent<ButtonDomRef, { prevSelectedItems: ListItemStandardDomRef[]; nativeDetail: number }>
) => void;
/**
* 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.
*/
Expand Down
Loading
Loading