import React from 'react'; import type { ReadonlyBinding } from '../binding/types/readonly-binding'; import { isBinding } from '../binding-utils/type-utils.js'; import { BindingsConsumer } from '../components/BindingsConsumer/index.js'; import { resolveTypeOrBindingType } from '../resolveable/utils.js'; import type { MakeBindableComponentOptions } from './types/make-bindable-component-options'; import type { UpgradeToBindingsProps } from './types/upgrade-to-binding-props'; /** * Take a functional component and creates a new functional component that takes binding props. The non-bound types are also still * accepted. * * The component will be re-rendered any time binding props change. * * The bindings are resolved before calling the original component. * * Bindings can also be passed through by specifying key names in the `passThru` option. */ export function makeBindableComponent, PassThruKeyT extends keyof PropsT & string = never>( functionalComponent: (props: PropsT) => React.JSX.Element ): (props: UpgradeToBindingsProps) => React.JSX.Element; export function makeBindableComponent, PassThruKeyT extends keyof PropsT & string = never>( options: MakeBindableComponentOptions, functionalComponent: (props: PropsT) => React.JSX.Element ): (props: UpgradeToBindingsProps) => React.JSX.Element; export function makeBindableComponent, PassThruKeyT extends keyof PropsT & string = never>( optionsOrFunctionalComponent: MakeBindableComponentOptions | ((props: PropsT) => React.JSX.Element), theFunctionalComponent?: (props: PropsT) => React.JSX.Element ): (props: UpgradeToBindingsProps) => React.JSX.Element { const { passThru, bindingsConsumerProps } = typeof optionsOrFunctionalComponent === 'function' ? ({} satisfies MakeBindableComponentOptions) : optionsOrFunctionalComponent; const functionalComponent = (typeof optionsOrFunctionalComponent === 'function' ? optionsOrFunctionalComponent : theFunctionalComponent)!; const passThruSet = new Set(passThru ?? []); return (props: UpgradeToBindingsProps) => { const bindings = getAllBindings(props, passThruSet); return bindings.length > 0 ? ( {() => functionalComponent(resolveBindings(props, passThruSet) as PropsT)} ) : ( functionalComponent(props as PropsT) ); }; } // Helpers const getAllBindings = (obj: Record, passThru: Set) => { const output: ReadonlyBinding[] = []; for (const [key, value] of Object.entries(obj)) { if (!passThru.has(key) && isBinding(value)) { output.push(value); } } return output; }; const resolveBindings = >(props: PropsT, passThru: Set): PropsT => Object.entries(props).reduce((out, [key, value]) => { if (passThru.has(key)) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment out[key as keyof PropsT] = value; return out; } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment out[key as keyof PropsT] = resolveTypeOrBindingType(value); return out; }, {} as Partial) as PropsT;