diff --git a/packages/components/src/utils/events.ts b/packages/components/src/utils/events.ts new file mode 100644 index 00000000000000..3ecc86eba7a029 --- /dev/null +++ b/packages/components/src/utils/events.ts @@ -0,0 +1,50 @@ +type EventHandler< T extends Event > = ( event: T ) => void; + +/** + * Merges event handlers together. + * + * @template TEvent + * @param handler + * @param otherHandler + */ +function mergeEvent< TEvent extends Event >( + handler: EventHandler< TEvent >, + otherHandler: EventHandler< TEvent > +): EventHandler< TEvent > { + return ( event: TEvent ) => { + if ( typeof handler === 'function' ) { + handler( event ); + } + if ( typeof otherHandler === 'function' ) { + otherHandler( event ); + } + }; +} + +/** + * Merges two sets of event handlers together. + * + * @template TEvent + * @param handlers + * @param extraHandlers + */ +export function mergeEventHandlers< + TEvent extends Event, + TLeft extends Record< string, EventHandler< TEvent > >, + TRight extends Record< string, EventHandler< TEvent > > +>( handlers: TLeft, extraHandlers: TRight ): TLeft & TRight { + // @ts-ignore We'll fill in all the properties below + const mergedHandlers: TLeft & TRight = { + ...handlers, + }; + + for ( const [ key, handler ] of Object.entries( extraHandlers ) ) { + // @ts-ignore + mergedHandlers[ key as keyof typeof mergedHandlers ] = + key in mergedHandlers + ? mergeEvent( mergedHandlers[ key ], handler ) + : handler; + } + + return mergedHandlers; +} diff --git a/packages/components/src/utils/test/events.js b/packages/components/src/utils/test/events.js new file mode 100644 index 00000000000000..75709614b9d072 --- /dev/null +++ b/packages/components/src/utils/test/events.js @@ -0,0 +1,63 @@ +/** + * Internal dependencies + */ +import { mergeEventHandlers } from '../events'; + +describe( 'mergeEventHandlers', () => { + it( 'should merge the two event handler objects', () => { + const left = { + onclick: jest.fn(), + }; + + const right = { + onclick: jest.fn(), + }; + + const merged = mergeEventHandlers( left, right ); + + merged.onclick(); + + expect( left.onclick ).toHaveBeenCalled(); + expect( right.onclick ).toHaveBeenCalled(); + } ); + + it( 'should preserve all handlers from the left hand side that do not overlap with the right', () => { + const left = { + ArrowUp: jest.fn(), + ArrowDown: jest.fn(), + }; + + const right = { + ArrowUp: jest.fn(), + }; + + const merged = mergeEventHandlers( left, right ); + + merged.ArrowUp(); + + expect( left.ArrowUp ).toHaveBeenCalled(); + expect( right.ArrowUp ).toHaveBeenCalled(); + + expect( merged.ArrowDown ).toBe( left.ArrowDown ); + } ); + + it( 'should preserve all handlers form the right hand side that do not overlap with the left', () => { + const right = { + ArrowUp: jest.fn(), + ArrowDown: jest.fn(), + }; + + const left = { + ArrowUp: jest.fn(), + }; + + const merged = mergeEventHandlers( left, right ); + + merged.ArrowUp(); + + expect( left.ArrowUp ).toHaveBeenCalled(); + expect( right.ArrowUp ).toHaveBeenCalled(); + + expect( merged.ArrowDown ).toBe( right.ArrowDown ); + } ); +} );