-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[core] Create
mergeReactProps
utility (#243)
- Loading branch information
Showing
8 changed files
with
234 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { expect } from 'chai'; | ||
import { spy } from 'sinon'; | ||
import { mergeReactProps } from './mergeReactProps'; | ||
|
||
describe('mergeReactProps', () => { | ||
it('merges event handlers', () => { | ||
const theirProps = { | ||
onClick: spy(), | ||
onKeyDown: spy(), | ||
}; | ||
const ourProps = { | ||
onClick: spy(), | ||
onPaste: spy(), | ||
}; | ||
const mergedProps = mergeReactProps<'button'>(theirProps, ourProps); | ||
|
||
mergedProps.onClick?.({} as any); | ||
mergedProps.onKeyDown?.({} as any); | ||
mergedProps.onPaste?.({} as any); | ||
|
||
expect(theirProps.onClick.calledBefore(ourProps.onClick)).to.equal(true); | ||
expect(theirProps.onClick.callCount).to.equal(1); | ||
expect(ourProps.onClick.callCount).to.equal(1); | ||
expect(theirProps.onKeyDown.callCount).to.equal(1); | ||
expect(ourProps.onPaste.callCount).to.equal(1); | ||
}); | ||
|
||
it('merges styles', () => { | ||
const theirProps = { | ||
style: { color: 'red' }, | ||
}; | ||
const ourProps = { | ||
style: { color: 'blue', backgroundColor: 'blue' }, | ||
}; | ||
const mergedProps = mergeReactProps<'div'>(theirProps, ourProps); | ||
|
||
expect(mergedProps.style).to.deep.equal({ | ||
color: 'red', | ||
backgroundColor: 'blue', | ||
}); | ||
}); | ||
|
||
it('merges styles with undefined', () => { | ||
const theirProps = { | ||
style: { color: 'red' }, | ||
}; | ||
const ourProps = { | ||
style: undefined, | ||
}; | ||
const mergedProps = mergeReactProps<'button'>(theirProps, ourProps); | ||
|
||
expect(mergedProps.style).to.deep.equal({ | ||
color: 'red', | ||
}); | ||
}); | ||
|
||
it('does not merge styles if both are undefined', () => { | ||
const theirProps = { | ||
style: undefined, | ||
}; | ||
const ourProps = { | ||
style: undefined, | ||
}; | ||
const mergedProps = mergeReactProps<'button'>(theirProps, ourProps); | ||
|
||
expect(mergedProps.style).to.equal(undefined); | ||
}); | ||
|
||
it('does not prevent internal handler if event.preventBaseUIHandler() is not called', () => { | ||
let ran = false; | ||
|
||
const mergedProps = mergeReactProps<'button'>( | ||
{ | ||
onClick() {}, | ||
}, | ||
{ | ||
onClick() { | ||
ran = true; | ||
}, | ||
}, | ||
); | ||
|
||
mergedProps.onClick?.({} as any); | ||
|
||
expect(ran).to.equal(true); | ||
}); | ||
|
||
it('prevents internal handler if event.preventBaseUIHandler() is called', () => { | ||
let ran = false; | ||
|
||
const mergedProps = mergeReactProps<'button'>( | ||
{ | ||
onClick(event) { | ||
event.preventBaseUIHandler(); | ||
}, | ||
}, | ||
{ | ||
onClick() { | ||
ran = true; | ||
}, | ||
}, | ||
); | ||
|
||
mergedProps.onClick?.({} as any); | ||
|
||
expect(ran).to.equal(false); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import type * as React from 'react'; | ||
import type { BaseUIEvent, WithBaseUIEvent } from './BaseUI.types'; | ||
|
||
/** | ||
* Merges two sets of React props such that their event handlers are called in sequence (the user's | ||
* before our internal ones), and allows the user to prevent the internal event handlers from being | ||
* executed by attaching a `preventBaseUIHandler` method. It also merges the `style` prop, whereby | ||
* the user's styles overwrite the internal ones. | ||
* @important **`className` and `ref` are not merged.** | ||
* @param externalProps the user's external props. | ||
* @param internalProps our own internal props. | ||
* @returns the merged props. | ||
*/ | ||
export function mergeReactProps<T extends React.ElementType>( | ||
externalProps: WithBaseUIEvent<React.ComponentPropsWithRef<T>>, | ||
internalProps: React.ComponentPropsWithRef<T>, | ||
): WithBaseUIEvent<React.ComponentPropsWithRef<T>> { | ||
return Object.entries(externalProps).reduce( | ||
(acc, [key, value]) => { | ||
if (/^on[A-Z]/.test(key) && typeof value === 'function') { | ||
acc[key] = (event: React.SyntheticEvent) => { | ||
let isPrevented = false; | ||
|
||
const theirHandler = value; | ||
const ourHandler = internalProps[key]; | ||
|
||
const baseUIEvent = event as BaseUIEvent<typeof event>; | ||
|
||
baseUIEvent.preventBaseUIHandler = () => { | ||
isPrevented = true; | ||
}; | ||
|
||
const result = theirHandler(baseUIEvent); | ||
|
||
if (!isPrevented) { | ||
ourHandler?.(baseUIEvent); | ||
} | ||
|
||
return result; | ||
}; | ||
} else if (key === 'style') { | ||
if (value || internalProps.style) { | ||
acc[key] = { ...internalProps.style, ...(value || {}) }; | ||
} | ||
} else { | ||
acc[key] = value; | ||
} | ||
|
||
return acc; | ||
}, | ||
{ ...internalProps }, | ||
); | ||
} |