UNPKG

@workday/canvas-kit-react

Version:

The parent module that contains all Workday Canvas Kit React components

88 lines (81 loc) 4.28 kB
import {mergeCallback} from './mergeCallback'; export type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T]; export type RemoveNulls<T> = Omit<T, KeysMatching<T, null>>; /** * MergeProps will merge keys from `U` over `T` except when the value of `T` is `null`. * * ```ts * MergeProps< * {foo: string, bar: string, baz: null}, * {foo: string, bar: number, baz: string} * > // { foo: string, bar: number, baz: null } * ``` */ export type MergeProps<T, U> = { // merge keys from both `T` and `U` [K in keyof T | keyof U]: K extends keyof T // test if key is in `T` ? K extends keyof U // test if key is also in `U` ? T[K] extends null // test if `T[K]` is `null` ? null // `K` is in both `T` and `U` and `T[K]` is `null`, so return `null` : U[K] // `K` is in both `T` and `U`, but isn't `null` in `T[K]`, so return `U[K]` : T[K] // `K` is only in `T`, so return `T[K]` : K extends keyof U // K is not in `T`, so test if it is in `U`. This should always match at this point, but there's no "else" in type ternaries ? U[K] // K is only in `U`, so return `U[K]` : never; // We should never get here, but type ternaries need all paths defined. `never` is usually used in these cases }; // This file suppresses TS errors that come from merging interfaces of elements that aren't // determined within components. Element interfaces are determined only when used, so TS errors // aren't even useful here. Things get complicated when merging interfaces of callbacks. /** * Merges source props into target props, overwriting target props that are also defined on source * props. Callback will be merged in such a way that both callbacks will be called. `css` and * `style` props will be merged in such a way that source props have the final override. * * If `targetProps` has a `null` set, it will remove the prop from the `sourceProps`. This allows * passing of props from merged hooks to another without passing out to the final element props. */ export function mergeProps<const T extends object, const S extends object>( targetProps: T, sourceProps: S ): RemoveNulls<MergeProps<T, S>> { const returnProps = {...targetProps} as MergeProps<T, S>; for (const key in sourceProps) { if (sourceProps.hasOwnProperty(key)) { if (key === 'css' && targetProps.hasOwnProperty('css')) { // @ts-ignore const css = [].concat(targetProps[key], sourceProps[key]); // @ts-ignore returnProps[key] = css; } else if (key === 'style' && targetProps.hasOwnProperty('style')) { // merge style objects // @ts-ignore TS complains that key might not be on targetProps, but typeof returns returnProps[key] = {...targetProps[key], ...sourceProps[key]}; } else if (key === 'ref' && sourceProps.hasOwnProperty('ref')) { // refs should never be overridden. This doesn't happen normally, but happens with hook // composition (returnProps as any).ref = (returnProps as any).ref || (sourceProps as any).ref; } else if ( typeof sourceProps[key] === 'function' && // @ts-ignore TS complains that key might not be on targetProps, but typeof returns // `undefined` in that case, so this is safe typeof targetProps[key] === 'function' ) { // @ts-ignore TS complains that T and S aren't compatible, but this isn't useful for us returnProps[key] = mergeCallback(targetProps[key], sourceProps[key]); } else if ( typeof sourceProps[key] === 'undefined' && // @ts-ignore T and S, blah, blah, blah typeof targetProps[key] === 'function' ) { // we don't support removing callbacks via `undefined`, so do nothing here // @ts-ignore Typescript complains that key might not exist in `targetProps` since we're iterating over sourceProps. At runtime this doesn't matter } else if (targetProps[key] === null) { // target props is trying to disable the prop for whatever reason. Consider `null` a "remove this prop" } else { // @ts-ignore TS has more object constraint complaints that aren't useful here returnProps[key] = sourceProps[key]; } } } return returnProps; }