import composeRefs from '@seznam/compose-react-refs' ; import { createElement , forwardRef , HTMLAttributes , useCallback , useEffect , useMemo , useRef , useState , } from 'react' ; export const define = ( tagName , clazz ) => { const isClient = typeof window !== 'undefined' ; if ( isClient && ! customElements . get ( tagName ) ) customElements . define ( tagName , clazz ) ; } ; const dashToPascalCase = ( str ) => str . toLowerCase ( ) . split ( '-' ) . map ( segment => segment . charAt ( 0 ) . toUpperCase ( ) + segment . slice ( 1 ) ) . join ( '' ) ; const isEvent = ( prop ) => prop . indexOf ( 'on' ) === 0 && prop [ 2 ] === prop [ 2 ] ?. toUpperCase ( ) ; const toDomEventName = ( prop ) => prop . charAt ( 2 ) . toLowerCase ( ) + prop . substring ( 3 ) ; export function createComponent < T extends HTMLElement , P extends HTMLAttributes < any > = { } > ( tagName , componentProps ) { const Component = forwardRef < T , P > ( ( { children , ... props } , forwardedRef ) => { const [ ref , setRef ] = useState < T | null > ( null ) ; const setRefCb = useCallback ( ( node ) => { setRef ( node ) ; } , [ ] ) ; const eventHandlers = useRef ( new Map ( ) ) ; const domProps = useMemo ( ( ) => Object . keys ( props ) . filter ( prop => ! componentProps . has ( prop ) && ! isEvent ( prop ) ) . reduce ( ( p , c ) => ( { ... p , [ c ] : ( props as any ) [ c ] } ) , { } ) , [ props ] ) ; const wcProps = useMemo ( ( ) => Object . keys ( props ) . filter ( prop => componentProps . has ( prop ) || isEvent ( prop ) ) . reduce ( ( p , c ) => ( { ... p , [ c ] : ( props as any ) [ c ] } ) , { } ) , [ props ] ) ; const listen = useCallback ( ( prop , handler ? ) => { const domEvent = toDomEventName ( prop ) ; eventHandlers . current . get ( domEvent ) ?. ( ) ; if ( ! ref || ! handler ) return ; ref ! . addEventListener ( domEvent , handler ) ; eventHandlers . current . set ( domEvent , ( ) => { ref ! . removeEventListener ( domEvent , handler ) ; } ) ; } , [ ref ] ) ; const cleanup = useCallback ( ( ) => { eventHandlers . current . forEach ( fn => fn ( ) ) ; eventHandlers . current . clear ( ) ; } , [ ] ) ; useEffect ( ( ) => ( ) => { cleanup ( ) ; } , [ ] ) ; useEffect ( ( ) => { if ( ! ref ) return ; Object . keys ( wcProps ) . forEach ( prop => { if ( isEvent ( prop ) ) { listen ( prop , ( wcProps as any ) [ prop ] ) ; return ; } if ( ( ref as any ) [ prop ] !== ( wcProps as any ) [ prop ] ) { ( ref as any ) [ prop ] = ( wcProps as any ) [ prop ] ; } } ) ; } , [ ref , wcProps ] ) ; return createElement ( tagName , { ref : composeRefs ( setRefCb , forwardedRef ) , ... domProps , } , children ) ; } ) ; Component . displayName = dashToPascalCase ( tagName ) ; return Component ; }