import React, { createRef, ElementType, forwardRef, ForwardRefExoticComponent, PropsWithChildren, PropsWithoutRef, RefAttributes, RefObject, useLayoutEffect, useMemo } from 'react' import mergeRefs from 'react-merge-refs' import { isComplexProp, isComponentType, isEventListener, listenerPropNameToEvent } from './util' export interface PropItem { key: string, value: T } export type PropType

= P & { [key: string]: any } export type Mappings

= { [K in keyof PropType

]?: string } export function useWebComponent ( props: PropType

, mapping: Mappings

= {}, eventsAreCamelCase: boolean = false ): [Partial

, RefObject] { const ref = createRef() const [probableEvents, probableProperties, returnProps] = useMemo(() => { const events: Array> = [] const properties: Array> = [] const returnProps: Partial

= {} for (const key in props) { const value = props[key] if (isEventListener(key, value)) events.push({ key: mapping[key] ?? listenerPropNameToEvent(key, eventsAreCamelCase), value }) else if (isComplexProp(value)) properties.push({ key: mapping[key] ?? key, value }) else returnProps[key as keyof P] = value } return [events, properties, returnProps] }, [props, mapping]) useLayoutEffect(() => { if (ref?.current == null) return const { current } = ref for (const event of probableEvents) { current.addEventListener(event.key, event.value) } for (const property of probableProperties) { current[property.key as keyof T] = property.value } return () => { for (const event of probableEvents) { current.removeEventListener(event.key, event.value) } } }, [probableEvents, probableProperties, ref]) return [returnProps, ref] } type HookProps

= PropType

& { mapping?: Mappings

, eventsAreCamelCase?: boolean } type ChildHookProps

= PropsWithChildren> function genClassList (className?: string, classProp?: string): string { return [className, classProp].filter(Boolean).join(' ') } export function withWebComponent (Component: ElementType): ForwardRefExoticComponent> & RefAttributes> { const WebComponent = forwardRef>(({ children, mapping = {}, eventsAreCamelCase = false, ...props }: ChildHookProps

, ref) => { const { className, class: classProp, ...restProps } = props const [simpleProps, innerRef] = useWebComponent(restProps, mapping, eventsAreCamelCase) return {children} }) const displayName = isComponentType(Component) ? Component.displayName : Component WebComponent.displayName = `WebComponent[${displayName ?? ''}]` return WebComponent }