Created
May 7, 2025 20:35
-
-
Save NicholasBoll/7bf2fe624e80ba04109b76b5c0160af2 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
import React from 'react'; | |
import { assert } from './assert'; | |
import { MergeProps } from './mergeProps'; | |
function defaultGetElemProps(input) { | |
return input; | |
} | |
export const createContainer = (as) => ({ displayName, modelHook, elemPropsHook, defaultContext, subComponents, }) => { | |
assert(modelHook.Context, 'createContainer only works on models with context. Please use `createModelHook` to create the `modelHook`'); | |
const Context = modelHook.Context; | |
return _jsxs(Props, { children: ["( Component: ( props: CompoundProps", _jsx(Props, {}), ", TElemPropsHook, E>, Element: E extends undefined ? never : E, model: TModelHook extends (config: infer TConfig) => infer TModel ? TModel : never ) => JSX.Element | null ): (TModelHook extends (config: infer TConfig) => infer TModel ? E extends undefined ? ComponentM", _jsx(Props, {}), " & TConfig, TModel> : ElementComponentM", _jsx( | |
// E is not `undefined` here, but Typescript thinks it could be, so we add another `undefined` | |
// check and cast to a `React.FC` to match a valid signature for `ElementComponent`. | |
// `React.FC` was chosen as the simplest valid interface. | |
E, { extends: true, undefined: true }), " ? React.FC : E, Props & TConfig, TModel > : never) & SubComponents => ", , "const ReturnedComponent = React.forwardRef", _jsx(E, {}), ", Props & ", as ? : React.ElementType, " & ", model ? : any, ">((", as, ": asOverride, model, ...props}, ref) => ", , "const localModel = useDefaultModel(model, props, modelHook, asOverride); const elemProps = ((modelHook as any).getElemProps || defaultGetElemProps)(props); const finalElemProps = elemPropsHook ? (elemPropsHook as any)(localModel, elemProps, ref) : elemProps; // make sure there's always a ref being passed, even if there are no elemProps hooks to run if (ref && !finalElemProps.hasOwnProperty('ref')) ", finalElemProps.ref = ref, "; } return React.createElement( Context.Provider,", value, ": localModel}, Component( finalElemProps, // Cast to `any` to avoid: \"ts(2345): Type 'undefined' is not assignable to type 'E extends // undefined ? never : E'\" I'm not sure I can actually cast to this conditional type and it // doesn't actually matter, so cast to `any` it is. (asOverride || as) as any, localModel ) ); }); Object.keys(subComponents || ", ").forEach(key => ", | |
// `ReturnedComponent` is a `React.ForwardRefExoticComponent` which has no additional keys so | |
// we'll cast to `Record<string, any>` for assignment. Note the lack of type checking | |
// properties. Take care when changing the runtime of this function. | |
ReturnedComponent[key] = subComponents[key], "; // Add a displayName if one isn't already created. This prevents us from having to add // `displayName` to subcomponents if a container component has a `displayName` if (displayName && !(subComponents as Record", _jsx("string", {}), ", any>)[key].displayName) ", subComponents[key].displayName = `${displayName}.${key}`, "; } }); ReturnedComponent.displayName = displayName; (ReturnedComponent as any).__hasModel = true; // The `any`s are here because `ElementComponent` takes care of the `as` type and the // `ReturnComponent` type is overridden (ReturnedComponent as any).as = memoize( (as: any) => createContainer(as)(", (displayName, subComponents, modelHook, elemPropsHook), ")( Component as any ), as => as ); // Cast as `any`. We have already specified the return type. Be careful making changes to this // file due to this `any` `ReturnedComponent` is a `React.ForwardRefExoticComponent`, but we want // it to be either an `Component` or `ElementComponent` return ReturnedComponent as any; }; }; /** * If elemProps returns `null` for a prop, that prop is to be removed from the prop * list. This is useful for passing props to an elemProp hook that should not be exposed * to the DOM element */ type RemoveNull", _jsxs(T, { children: [" = ", [K in keyof, T], ": Exclude", _jsx(T, {}), "[K], null>}; /** * Props for the compound component based on props, elemPropsHook, and element. It will * return a prop interface according to all these inputs. The following will be added to * the passed in `Props` type: * - The prop interface returned by the `elemPropsHook` function * - if there is no detected `ref` in the `Props` interface, a `ref` will be added based on the element type E * - if there is no detected `children` in the `Props` interface, `children` will be added based on `ReactNode` */ type CompoundProps", _jsx(Props, {}), ", TElemPropsHook, E> = Props & (TElemPropsHook extends (...args: any[]) => infer TProps // try to infer TProps returned from the elemPropsHook function ? RemoveNull", _jsx(Omit, {}), " & ", ref, ": ExtractRef", _jsxs(E, { children: ["}> : ", ref, ": ExtractRef", _jsxs(E, { children: ["}) & (Props extends ", children, ": any} ? ", ": ", children ? : React.ReactNode, "; }); export const createSubcomponent =", _jsx(E, { extends: true }), "| keyof JSX.IntrinsicElements | React.ComponentType | ElementComponent", _jsx("any", {}), ", any> | ElementComponentM", _jsx("any", {}), ", any, any> | undefined = undefined, >( as?: E ) =>", _jsx(TElemPropsHook, {}), ", // normally we'd put a constraint here, but doing so causes the `infer` below to fail to infer the return props TModelHook extends ((config: any) => Model", _jsx("any", {}), ", any>) & ", Context ? : React.Context, ", SubComponents = ", ", >(", (displayName, | |
modelHook, | |
elemPropsHook, | |
subComponents, | |
), ": ", | |
/** @deprecated ⚠️ `displayName` has been deprecated and will be removed in a future major version. You no longer need to use `displayName`. A `displayName` will be automatically added if it belongs to a container. */ | |
displayName ? : string, "; modelHook: TModelHook; elemPropsHook?: TElemPropsHook; subComponents?: SubComponents; }) => ", assert(modelHook.Context, 'createSubcomponent only works on models with context. Please use `createModelHook` to create the `modelHook`'), "; return ", _jsx(Props, {}), " = ", ">( Component: ( props: CompoundProps", _jsx(Props, {}), ", TElemPropsHook, E>, Element: E extends undefined ? never : E, model: TModelHook extends (...args: any[]) => infer TModel ? TModel : never ) => JSX.Element | null ): (TModelHook extends (...args: any[]) => infer TModel ? ElementComponentM", _jsx( | |
// E is not `undefined` here, but Typescript thinks it could be, so we add another `undefined` | |
// check and cast to a `React.FC` to match a valid signature for `ElementComponent`. | |
// `React.FC` was chosen as the simplest valid interface. | |
E, { extends: true, undefined: true }), " ? React.FC : E, Props, TModel > : never) & SubComponents => ", , "const ReturnedComponent = React.forwardRef", _jsx(E, {}), ", Props & ", as ? : React.ElementType, " & ", model ? : any, "; elemPropsHook?: (...args: any) => any} >((", as, ": asOverride, model, elemPropsHook: additionalPropsHook, ...props}, ref) => ", , "const localModel = useModelContext(modelHook.Context!, model, asOverride); // maybeModelProps reattached the `model` prop if the passed model is incompatible with the // modelHook's context. This fixes issues when using the `as` prop on model element components // that both have a model const maybeModelProps = model && localModel !== model ? ", ...(props, model, ref), " : ", ...(props, ref), "; const elemProps = elemPropsHook ? (elemPropsHook as any)(localModel, maybeModelProps, ref) : maybeModelProps; return Component( additionalPropsHook ? additionalPropsHook(localModel, elemProps, ref) : elemProps, // Cast to `any` to avoid: \"ts(2345): Type 'undefined' is not assignable to type 'E extends // undefined ? never : E'\" I'm not sure I can actually cast to this conditional type and it // doesn't actually matter, so cast to `any` it is. (asOverride || as) as any, localModel ); }); Object.keys(subComponents || ", ").forEach(key => ", | |
// `ReturnedComponent` is a `React.ForwardRefExoticComponent` which has no additional keys so | |
// we'll cast to `Record<string, any>` for assignment. Note the lack of type checking | |
// properties. Take care when changing the runtime of this function. | |
ReturnedComponent[key] = subComponents[key], "; }); if (displayName) ", ReturnedComponent.displayName = displayName, "; } (ReturnedComponent as any).__hasModel = true; // The `any`s are here because `ElementComponent` takes care of the `as` type and the // `ReturnComponent` type is overridden (ReturnedComponent as any).as = memoize( (as: any) => createSubcomponent(as)(", (displayName, subComponents, modelHook, elemPropsHook), ")( Component as any ), as => as ); // Cast as `any`. We have already specified the return type. Be careful making changes to this // file due to this `any` `ReturnedComponent` is a `React.ForwardRefExoticComponent`, but we want // it to be either an `Component` or `ElementComponent` return ReturnedComponent as any; }; }; /** * Factory function that creates components to be exported. It enforces React ref forwarding, `as` * prop, display name, and sub-components, and handles proper typing without much boiler plate. The * return type is `Component", _jsx("element", {}), ", Props>` which looks like `Component", _jsx(, {}), "'div', Props>` which is a * clean interface that tells you the default element that is used. */ export const createComponent =", _jsx(E, { extends: true }), "| keyof JSX.IntrinsicElements | React.ComponentType | ElementComponent", _jsx("any", {}), ", any> | undefined = undefined, >( as?: E ) =>", _jsx(P, {}), ", SubComponents = ", ">(", (displayName, | |
Component, | |
subComponents, | |
), ": ", | |
/** This is what the component will look like in the React dev tools. Encouraged to more easily | |
* understand the component tree */ | |
displayName ? : string, "; /** The component function. The function looks like: * @example * Component: (", children, ", ref, Element) ", | |
* | |
// `Element` is what's passed to the `as` of your component. If no `as` was defined, it | |
* | |
// will be the default element. It will be 'div' or even a another Component! | |
* , " return ( * ", _jsx(Element, { ref: ref, children: children }), "* ) * } * * @example * Component: (", children, ", ref, Element) ", | |
* | |
// `Element` can be passed via `as` to the next component | |
* , " return ( * ", _jsx(AnotherElement, { as: Element, ref: ref, children: children }), "* ) * } */ Component: RefForwardingComponent", _jsx(E, {}), ", P>; /** * Used in container components */ subComponents?: SubComponents; }): (E extends undefined ? Component", _jsxs(P, { children: [": ElementComponent", _jsx( | |
// E is not `undefined` here, but Typescript thinks it could be, so we add another `undefined` | |
// check and cast to a `React.FC` to match a valid signature for `ElementComponent`. | |
// `React.FC` was chosen as the simplest valid interface. | |
E, { extends: true, undefined: true }), " ? React.FC : E, P >) & SubComponents => ", , "const ReturnedComponent = React.forwardRef", _jsx(E, {}), ", P & ", as ? : React.ElementType, ">( (", as, ": asOverride, ...props}, ref) => ", , "return Component( props as any, ref as ExtractRef", _jsxs(E, { children: [", // Cast to `any` to avoid: \"ts(2345): Type 'undefined' is not assignable to type 'E extends // undefined ? never : E'\" I'm not sure I can actually cast to this conditional type and it // doesn't actually matter, so cast to `any` it is. (asOverride || as) as any ); } ); Object.keys(subComponents || ", ").forEach(key => ", | |
// `ReturnedComponent` is a `React.ForwardRefExoticComponent` which has no additional keys so | |
// we'll cast to `Record<string, any>` for assignment. Note the lack of type checking | |
// properties. Take care when changing the runtime of this function. | |
ReturnedComponent[key] = subComponents[key], "; }); ReturnedComponent.displayName = displayName; // The `any`s are here because `ElementComponent` takes care of the `as` type and the // `ReturnComponent` type is overridden (ReturnedComponent as any).as = memoize( (as: any) => createComponent(as)(", (displayName, Component, subComponents), "), as => as ); // Cast as `any`. We have already specified the return type. Be careful making changes to this // file due to this `any` `ReturnedComponent` is a `React.ForwardRefExoticComponent`, but we want // it to be either an `Component` or `ElementComponent` return ReturnedComponent as any; }; /** * An `elemPropsHook` is a React hook that takes a model, ref, and elemProps and returns props and * attributes to be spread to an element or component. * * ```tsx * const useMyHook = createElemPropsHook(useMyModel)((model) => ", | |
* , " return ", | |
* id, ": model.state.id * } * }) * ``` * * **Note:** If your hook needs to use a ref, it must be forked using `useLocalRef` or `useForkRef` * and return the forked ref: * * ```tsx * const useMyHook = createElemPropsHook(useMyModel)((model, ref, elemProps) => ", | |
* , " const ", (localRef, elementRef), " = useLocalRef(ref); * * React.useLayoutEffect(() => ", | |
* console.log('element', localRef.current) // logs the DOM element | |
* , ", []) * * return ", | |
* ref, ": elementRef * } * }) * ``` */ export const createElemPropsHook =", _jsx(TModelHook, { extends: true }), " (config: any) => Model", _jsx("any", {}), ", any>>(modelHook: TModelHook) =>", _jsx("const", { PO: true, extends: true, ... }), ", const PI extends ", ">( fn: ( model: TModelHook extends (config: any) => infer TModel ? TModel : Model", _jsx("any", {}), ", any>, ref?: React.Ref", _jsxs("unknown", { children: [", elemProps?: PI ) => PO ): BehaviorHook", _jsx(TModelHook, { extends: true }), " (config: any) => infer TModel ? TModel : Model", _jsx("any", {}), ", any>, PO > => ", , "return ((model, elemProps, ref) => ", , "const props = mergeProps(fn(model, ref, elemProps || (", " as any)), elemProps || (", " as any)); if (!props.hasOwnProperty('ref') && ref) ", | |
// This is the weird "incoming ref isn't in props, but outgoing ref is in props" thing | |
// @ts-ignore TS says `ref` isn't on `PO`, but we always add it anyways | |
props.ref = ref, "; } return props; }) as BehaviorHook", _jsx(TModelHook, { extends: true }), " (config: any) => infer TModel ? TModel : Model", _jsx("any", {}), ", any>, PO >; }; /** * Factory function to crate a behavior hook with correct generic types. It takes a function that * return props and returns a function that will also require `elemProps` and will call `mergeProps` for * you. If your hook makes use of the `ref`, you will have to also use `useLocalRef` to properly fork * the ref. * * @example * const useMyHook = createHook((model: MyModel, ref) => ", | |
* , " const ", (localRef, elementRef), " = useLocalRef(ref); * // do whatever with `localRef` which is a RefObject * * return ", | |
* onClick, ": model.events.doSomething, * ref: elementRef, * }; * }); * * // Equivalent to: * const useMyHook = ", _jsxs(P, { extends: true, ..., children: ["( * model: MyModel, * elemProps: P, * ref: React.Ref", _jsxs("unknown", { children: ["* ) => ", | |
* , " const ", (localRef, elementRef), " = useLocalRef(ref); * // do whatever with `localRef` which is a RefObject * * return mergeProps(", | |
* onClick, ": model.events.doSomething, * ref: elementRef, * }, elemProps); * }; * * @param fn Function that takes a model and optional ref and returns props */ export const createHook = ", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, PO extends ", ", PI extends ", ">( fn: (model: M, ref?: React.Ref", _jsxs("unknown", { children: [", elemProps?: PI) => PO ): BehaviorHook", _jsx(M, {}), ", PO> => ", , "return ((model, elemProps, ref) => ", , "const props = mergeProps(fn(model, ref, elemProps || (", " as any)), elemProps || (", " as any)); if (!props.hasOwnProperty('ref') && ref) ", | |
// This is the weird "incoming ref isn't in props, but outgoing ref is in props" thing | |
// @ts-ignore TS says `ref` isn't on `PO`, but we always add it anyways | |
props.ref = ref, "; } return props; }) as BehaviorHook", _jsx(M, {}), ", PO>; }; /** * @deprecated \u26A0\uFE0F `subModelHook` has been deprecated and will be removed in a future major version. Please use `createSubModelElemPropsHook` instead. */ export const subModelHook = ", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, SM extends Model", _jsx("any", {}), ", any>, O extends ", ">( fn: (model: M) => SM, hook: BehaviorHook", _jsx(SM, {}), ", O> ): BehaviorHook", _jsx(M, {}), ", O> => ", , "return ((model: M, props: any, ref: React.Ref", _jsxs("unknown", { children: [") => ", , "return hook(fn(model), props, ref); }) as BehaviorHook", _jsx(M, {}), ", O>; }; /** * Creates an elemPropsHook that returns the elemProps from another hook that is meant for a * subModel. Usually only used when composing elemProps hooks. * * For example: * * ```tsx * const useMySubModel = () => ", "* * const useMyModel = () => ", | |
* , " const subModel = useMySubModel() * * return ", ( | |
* state, | |
* events, | |
* subModel, | |
* ), "* } * * const useMyComponent = composeHook( * createElemPropsHook(useMyModel)(model => (", id, ": '' })), * createSubModelElemPropsHook(useMyModel)(m => m.subModel, useSomeOtherComponent) * ) * ``` */ export function createSubModelElemPropsHook", _jsx(M, { extends: true }), " () => Model", _jsx("any", {}), ", any>>(modelHook: M) ", , "return ", _jsx(SM, { extends: true, Model: true }), _jsx("any", {}), ", any>, O extends ", ">( fn: (model: ReturnType", _jsxs(M, { children: [") => SM, elemPropsHook: BehaviorHook", _jsx(SM, {}), ", O> ): BehaviorHook", _jsx(ReturnType, {}), ", O> => ", , "return ((model: ReturnType", _jsxs(M, { children: [", props: any, ref: React.Ref", _jsxs("unknown", { children: [") => ", , "return elemPropsHook(fn(model), props, ref); }) as BehaviorHook", _jsx(ReturnType, {}), ", O>; }; } /** Simplify and speed up inference by capturing types in the signature itself */ interface BaseHook", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, O extends ", "> ", | |
/** | |
* Capture the model type in TypeScript only. Do not use in runtime! | |
* | |
* @private | |
*/ | |
__model, ": M; /** * Capture the hook's output type in TypeScript only. Do not use in runtime! This is used to cache * and speed up the output types during inference * * @private */ __output: O; } // TypeScript function parameters are contravariant while return types are covariant. This is a // problem when someone hands us a model that correctly extends `Model", _jsx("any", {}), ", any>`, but adds extra // properties to the model. So `M extends Model", _jsx("any", {}), ", any>`. But the `BehaviorHook` is the return // type which will reverse the direction which is no longer true: `Model", _jsx("any", {}), ", any> extends M`. In // order to avoid this issue, we use the `bivarianceHack` found in ReactJS type definitions. This // hack forces Typescript to treat `M` as a bivariant allowing extension to go either direction. // Normally this would be less type safe, but we're using a generic `M` as a placeholder so there // isn't a real issue. Not 100% this is a bug, but the \"hack\" is a bit messy. // https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance // https://stackoverflow.com/questions/52667959/what-is-the-purpose-of-bivariancehack-in-typescript-types/52668133 /** * A BehaviorHook is a React hook that takes a model, elemProps, and a ref and returns props and * attributes to apply to an element or component. */ export interface BehaviorHook", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, O extends ", "> extends BaseHook", _jsx(M, {}), ", O> ", (model, elemProps, ref) => , "; } function setRef", _jsxs(T, { children: ["(ref: React.Ref", _jsxs(T, { children: [" | undefined, value: T): void ", , "if (ref) ", , "if (typeof ref === 'function') ", ref(value), "; } else ", | |
// Refs are readonly, but we can technically write to it without issue | |
ref.current = value, "; } } } /** * This function will create a new forked ref out of two input Refs. This is useful for components * that use `React.forwardRef`, but also need internal access to a Ref. * * This function is inspired by https://www.npmjs.com/package/@rooks/use-fork-ref * * @example * React.forwardRef((props, ref) => ", | |
* | |
// Returns a RefObject with a `current` property | |
* , " const myRef = React.useRef(ref) * * // Returns a forked Ref function to pass to an element. * // This forked ref will update both `myRef` and `ref` when React updates the element ref * const elementRef = useForkRef(ref, myRef) * * useEffect(() => ", | |
* console.log(myRef.current) // `current` is the DOM instance | |
* | |
// `ref` might be null since it depends on if someone passed a `ref` to your component | |
* | |
// `elementRef` is a function and we cannot get a current value out of it | |
* , ") * * return ", _jsx("div", { ref: elementRef }), "* }) */ export function useForkRef", _jsxs(T, { children: ["(ref1?: React.Ref", _jsxs(T, { children: [", ref2?: React.Ref", _jsxs(T, { children: ["): React.RefCallback", _jsxs(T, { children: [" ", , "return (value: T) => ", setRef(ref1, value), "; setRef(ref2, value); }; } /** * This functions handles the common use case where a component needs a local ref and needs to * forward a ref to an element. * @param ref The React ref passed from the `createComponent` factory function * * @example * const MyComponent = (", (children, ), " ...elemProps}: MyProps, ref, Element) => ", | |
* , " const ", (localRef, elementRef), " = useLocalRef(ref); * * // do something with `localRef` which is a `RefObject` with a `current` property * * return ", _jsx(Element, { ref: elementRef, ...elemProps }), "* } */ export function useLocalRef", _jsxs(T, { children: ["(ref?: React.Ref", _jsxs(T, { children: [") ", , "const localRef = React.useRef", _jsxs(T, { children: ["(null); const elementRef = useForkRef(ref, localRef); return ", (localRef, elementRef), "; } /** * Returns a model, or calls the model hook with config. Clever way around the conditional React * hook ESLint rule. * @param model A model, if provided * @param config Config for a model * @param modelHook A model hook that takes valid config * @example * const ContainerComponent = (", (children, model, ), " ...config}: ContainerProps) => ", | |
* , " const value = useDefaultModel(model, config, useContainerModel); * * // ... * } */ export function useDefaultModel", _jsx(T, {}), ", C>( model: T | undefined, config: C, modelHook: (config: C) => T, as?: React.ElementType ) ", | |
// Make sure we don't pass the `model` to a component if it is incompatible with that component. | |
// Otherwise we'll have strange runtime failures when a component or elemProps hooks try to | |
// access the `state` or `events` | |
, "// Make sure we don't pass the `model` to a component if it is incompatible with that component. // Otherwise we'll have strange runtime failures when a component or elemProps hooks try to // access the `state` or `events` if ( !model || (as && (as as any).__hasModel && (model as any).__UNSTABLE_modelContext !== (modelHook as any).Context) ) ", , "return modelHook(config); } return model; } /** * Returns a model, or returns a model context. Clever way around the conditional React hook ESLint * rule * @param model A model, if provided * @param context The context of a model * @example * const SubComponent = (", (children, model, ), " ...elemProps}: SubComponentProps, ref, Element) => ", | |
* , " const ", (state, events), " = useModelContext(model, SubComponentModelContext, Element); * * // ... * } */ export function useModelContext", _jsxs(T, { children: ["( context: React.Context", _jsxs(T, { children: [", model?: T, as?: React.ElementType ): T ", , "const contextModel = React.useContext(context); if ( !model || (as && (as as any).__hasModel && (model as any).__UNSTABLE_modelContext !== context) ) ", , "return contextModel; } return model; } /** * Compose many hooks together. Each hook should make a call to `mergeProps` which is automatically * done by `createElemPropsHook` and `createHook. Returns a function that will receive a model and * return props to be applied to a component. Hooks run from last to first, but props override from * first to last. This means the last hook will run first, passing `elemProps` to the next last * hook. There is a special exception, which is `null`. `null` means \"remove this prop\" and the null * handling takes precedence to the first. Take care when using `null` as it will remove props * passed in even from the developer. It can be useful when passing data between composed hooks or * then redirecting a prop somewhere else. * * For example: * * ```ts * const useHook1 = createElemPropsHook(useMyModel)((model, ref, elemProps) => ", | |
* console.log('useHook1', elemProps) | |
* , " return ", | |
* a, ": 'useHook1', * c: 'useHook1', * d: null, // remove the `d` prop * } * }) * * const useHook2 = createElemPropsHook(useMyModel)((model, ref, elemProps) => ", | |
* console.log('useHook2', elemProps) | |
* , " return ", | |
* b, ": 'useHook2', * c: 'useHook2', * d: 'useHook2', * } * }) * * const useHook3 = composeHooks( * useHook1, // run last, will have access to `useHook2`'s elemProps, but can remove a prop with `null` * useHook2 // run first and will override all of `useHook1`'s props * ) * const props = useHook3(model, ", c, ": 'props', d: 'props' }) * console.log('props', props) * ``` * * The output would be: * * ```ts * useHook2 ", c, ": 'props', d: 'props'} * useHook1 ", b, ": 'useHook2', c: 'props', d: 'props'} * props ", a, ": 'useHook1', b: 'useHook2', c: 'props', d: null} * ``` */ export function composeHooks", _jsx(H1, { extends: true, BaseHook: true }), _jsx("any", {}), ", ", ">, H2 extends BaseHook", _jsx("any", {}), ", ", ">, H3 extends BaseHook", _jsx("any", {}), ", ", ">, H4 extends BaseHook", _jsx("any", {}), ", ", ">, H5 extends BaseHook", _jsx("any", {}), ", ", ">, H6 extends BaseHook", _jsx("any", {}), ", ", ">, H7 extends BaseHook", _jsx("any", {}), ", ", ">, >( hook1: H1, hook2: H2, hook3?: H3, hook4?: H4, hook5?: H5, hook6?: H6, hook7?: H7, // TypeScript will only infer up to 6, but the types will still exist for those 6. The rest of the // hooks won't add to the interface, but that seems to be an okay fallback ...hooks: BehaviorHook", _jsx("any", {}), ", any>[] ): H1 extends BaseHook", _jsx("infer", { M: true }), ", infer O1> ? H2 extends BaseHook", _jsx("any", {}), ", infer O2> ? H3 extends BaseHook", _jsx("any", {}), ", infer O3> ? H4 extends BaseHook", _jsx("any", {}), ", infer O4> ? H5 extends BaseHook", _jsx("any", {}), ", infer O5> ? H6 extends BaseHook", _jsx("any", {}), ", infer O6> ? H7 extends BaseHook", _jsx("any", {}), ", infer O7> ? BehaviorHook", _jsx(M, {}), ", RemoveNulls", _jsxs(MergeProps, { children: ["> : never : never : never : never : never : never : never; export function composeHooks", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, P extends ", ", O extends ", ">( ...hooks: ((model: M, props: P, ref: React.Ref", _jsxs("unknown", { children: [") => O)[] ): BehaviorHook", _jsx(M, {}), ", O> ", , "return ((model, props, ref) => ", , "const returnProps = [...hooks].reverse().reduce((props: any, hook) => ", , "return hook(model, props, props.ref || ref); }, props); // remove null props values for (const key in returnProps) ", , "if (returnProps[key] === null) ", delete returnProps[key], "; } } if (!returnProps.hasOwnProperty('ref') && ref) ", | |
// This is the weird "incoming ref isn't in props, but outgoing ref is in props" thing | |
returnProps.ref = ref, "; } return returnProps; }) as BehaviorHook", _jsx(M, {}), ", O>; }"] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] }); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment