import type { JSX, Signal } from 'solid-js'; import { createEffect, createSignal } from 'solid-js'; import type { OmitAndMerge } from './types'; export type ValidElements = keyof JSX.IntrinsicElements; export type ValidComponent

= (props: P) => JSX.Element; export type ValidConstructor = | ValidElements | ValidComponent | (string & {}); export type DynamicProps = T extends ValidElements ? JSX.IntrinsicElements[T] : T extends ValidComponent ? U : Record; type UnboxIntrinsicElements = T extends JSX.HTMLAttributes ? U : never; type RefCallback = (el: T) => void; type RefField = T | RefCallback; type UnboxComponentProp = U extends { ref: infer X } ? X : never; export type DynamicNode = T extends ValidElements ? UnboxIntrinsicElements : T extends ValidComponent ? UnboxComponentProp : never; // Just a dynamic way to make a `ref` property // based on the constructor export interface WithRef { ref?: RefField>; } export interface DynamicComponent { as?: T; } export interface DynamicComponentWithRef extends WithRef { as?: T; } export type HeadlessProps = OmitAndMerge< V & DynamicComponent, DynamicProps >; export type HeadlessPropsWithRef< T extends ValidConstructor, V = {}, > = OmitAndMerge, DynamicProps>; function isRefFunction( callback?: RefField>, ): callback is RefCallback> { return typeof callback === 'function'; } // `props.ref` could have been used however it doesn't enforce // proper timing. We want to make sure that `ref` is called in // a way that it behaves the same way as if it were called // natively on an element (which runs in the same scope as the component) // This is useful if the ref function itself has ownership-based calls // like createEffect export function createForwardRef( props: WithRef, ): Signal | undefined> { const [ref, setRef] = createSignal>(); createEffect(() => { const current = ref(); // Technically Solid compiles refs on components into // a function, despite the fact that its type definition // says that it is either a function or the ref type if (current && 'ref' in props && isRefFunction(props.ref)) { props.ref(current); } }); return [ref, setRef]; }