diff --git a/.changeset/wild-parents-matter.md b/.changeset/wild-parents-matter.md new file mode 100644 index 000000000..a36e1b958 --- /dev/null +++ b/.changeset/wild-parents-matter.md @@ -0,0 +1,5 @@ +--- +"@cube-dev/ui-kit": patch +--- + +Don't pass onPress prop to the element in ItemButton. diff --git a/src/components/actions/ItemButton/ItemButton.tsx b/src/components/actions/ItemButton/ItemButton.tsx index 977ee5b4b..865d77ab2 100644 --- a/src/components/actions/ItemButton/ItemButton.tsx +++ b/src/components/actions/ItemButton/ItemButton.tsx @@ -24,7 +24,7 @@ export const ItemButton = forwardRef(function ItemButton( allProps: CubeItemButtonProps, ref: FocusableRef, ) { - const { mods, to, htmlType, as, type, theme, ...rest } = + const { mods, to, htmlType, as, type, theme, onPress, ...rest } = allProps as CubeItemButtonProps & { as?: 'a' | 'button' | 'div' | 'span'; }; diff --git a/src/components/actions/use-action.test.tsx b/src/components/actions/use-action.test.tsx index c2d375909..e6db73258 100644 --- a/src/components/actions/use-action.test.tsx +++ b/src/components/actions/use-action.test.tsx @@ -277,6 +277,26 @@ describe('performClickHandler', () => { ...modifiers, }); + it('passes a serializable event to onPress (target preserved, non-enumerable)', () => { + const evt = createMockEvent(); + + performClickHandler(evt, { + navigate: mockNavigate, + resolvedHref: undefined, + to: undefined, + onPress: mockOnPress, + tracking: mockTracking, + navigationOptions: {}, + }); + + expect(mockOnPress).toHaveBeenCalled(); + const arg = mockOnPress.mock.calls[0][0]; + expect(() => JSON.stringify(arg)).not.toThrow(); + expect(arg.target).toBe(evt.target); + expect(arg.shiftKey).toBe(false); + expect(arg.metaKey).toBe(false); + }); + it('should handle history navigation', () => { const evt = createMockEvent(); diff --git a/src/components/actions/use-action.ts b/src/components/actions/use-action.ts index 414dcd4da..09f793683 100644 --- a/src/components/actions/use-action.ts +++ b/src/components/actions/use-action.ts @@ -140,6 +140,44 @@ export function parseTo(to: NavigateArg | undefined): { }; } +interface SanitizedPressEvent { + type?: string; + pointerType?: string; + shiftKey: boolean; + metaKey: boolean; + ctrlKey: boolean; + altKey: boolean; + // target is defined as a non-enumerable property below + [key: string]: unknown; // allow non-enumerable 'target' +} + +function sanitizePressEvent(evt: PressEvent): SanitizedPressEvent { + const safeEvt: SanitizedPressEvent = { + type: 'type' in evt ? evt.type : undefined, + pointerType: + 'pointerType' in evt + ? (evt as { pointerType?: string }).pointerType + : undefined, + shiftKey: + 'shiftKey' in evt ? !!(evt as { shiftKey?: boolean }).shiftKey : false, + metaKey: + 'metaKey' in evt ? !!(evt as { metaKey?: boolean }).metaKey : false, + ctrlKey: + 'ctrlKey' in evt ? !!(evt as { ctrlKey?: boolean }).ctrlKey : false, + altKey: 'altKey' in evt ? !!(evt as { altKey?: boolean }).altKey : false, + }; + try { + Object.defineProperty(safeEvt, 'target', { + value: 'target' in evt ? (evt as { target?: unknown }).target : undefined, + enumerable: false, + configurable: true, + }); + } catch (e) { + // Failed to define target property - continue without it + } + return safeEvt; +} + export function performClickHandler( evt, { navigate, resolvedHref, to, onPress, tracking, navigationOptions }, @@ -156,7 +194,7 @@ export function performClickHandler( const element = evt.target; const qa = element?.getAttribute('data-qa'); - onPress?.(evt); + onPress?.(sanitizePressEvent(evt)); if (to == null) { // Allow 0 as valid navigation (go to current page)